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#include "GrAtlasTextContext.h"
8
9#include "GrDrawContext.h"
10#include "GrDrawTarget.h"
11#include "GrTextBlobCache.h"
12#include "GrTextUtils.h"
13
14#include "SkDraw.h"
15#include "SkDrawFilter.h"
16#include "SkGrPriv.h"
17
18GrAtlasTextContext::GrAtlasTextContext()
19    : fDistanceAdjustTable(new GrDistanceFieldAdjustTable) {
20}
21
22
23GrAtlasTextContext* GrAtlasTextContext::Create() {
24    return new GrAtlasTextContext();
25}
26
27bool GrAtlasTextContext::canDraw(const SkPaint& skPaint,
28                                 const SkMatrix& viewMatrix,
29                                 const SkSurfaceProps& props,
30                                 const GrShaderCaps& shaderCaps) {
31    return GrTextUtils::CanDrawAsDistanceFields(skPaint, viewMatrix, props, shaderCaps) ||
32           !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
33}
34
35GrColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
36    GrColor canonicalColor = paint.computeLuminanceColor();
37    if (lcd) {
38        // This is the correct computation, but there are tons of cases where LCD can be overridden.
39        // For now we just regenerate if any run in a textblob has LCD.
40        // TODO figure out where all of these overrides are and see if we can incorporate that logic
41        // at a higher level *OR* use sRGB
42        SkASSERT(false);
43        //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
44    } else {
45        // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
46        // gamma corrected masks anyways, nor color
47        U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
48                                       SkColorGetG(canonicalColor),
49                                       SkColorGetB(canonicalColor));
50        // reduce to our finite number of bits
51        canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
52    }
53    return canonicalColor;
54}
55
56// TODO if this function ever shows up in profiling, then we can compute this value when the
57// textblob is being built and cache it.  However, for the time being textblobs mostly only have 1
58// run so this is not a big deal to compute here.
59bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) {
60    SkTextBlobRunIterator it(blob);
61    for (; !it.done(); it.next()) {
62        if (it.isLCD()) {
63            return true;
64        }
65    }
66    return false;
67}
68
69void GrAtlasTextContext::drawTextBlob(GrContext* context, GrDrawContext* dc,
70                                      const GrClip& clip, const SkPaint& skPaint,
71                                      const SkMatrix& viewMatrix,
72                                      const SkSurfaceProps& props, const SkTextBlob* blob,
73                                      SkScalar x, SkScalar y,
74                                      SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
75    // If we have been abandoned, then don't draw
76    if (context->abandoned()) {
77        return;
78    }
79
80    SkAutoTUnref<GrAtlasTextBlob> cacheBlob;
81    SkMaskFilter::BlurRec blurRec;
82    GrAtlasTextBlob::Key key;
83    // It might be worth caching these things, but its not clear at this time
84    // TODO for animated mask filters, this will fill up our cache.  We need a safeguard here
85    const SkMaskFilter* mf = skPaint.getMaskFilter();
86    bool canCache = !(skPaint.getPathEffect() ||
87                      (mf && !mf->asABlur(&blurRec)) ||
88                      drawFilter);
89
90    GrTextBlobCache* cache = context->getTextBlobCache();
91    if (canCache) {
92        bool hasLCD = HasLCD(blob);
93
94        // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
95        SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
96                                                 kUnknown_SkPixelGeometry;
97
98        // TODO we want to figure out a way to be able to use the canonical color on LCD text,
99        // see the note on ComputeCanonicalColor above.  We pick a dummy value for LCD text to
100        // ensure we always match the same key
101        GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
102                                          ComputeCanonicalColor(skPaint, hasLCD);
103
104        key.fPixelGeometry = pixelGeometry;
105        key.fUniqueID = blob->uniqueID();
106        key.fStyle = skPaint.getStyle();
107        key.fHasBlur = SkToBool(mf);
108        key.fCanonicalColor = canonicalColor;
109        cacheBlob.reset(SkSafeRef(cache->find(key)));
110    }
111
112    // Though for the time being runs in the textblob can override the paint, they only touch font
113    // info.
114    GrPaint grPaint;
115    if (!SkPaintToGrPaint(context, skPaint, viewMatrix, &grPaint)) {
116        return;
117    }
118
119    if (cacheBlob) {
120        if (cacheBlob->mustRegenerate(skPaint, grPaint.getColor(), blurRec, viewMatrix, x, y)) {
121            // We have to remake the blob because changes may invalidate our masks.
122            // TODO we could probably get away reuse most of the time if the pointer is unique,
123            // but we'd have to clear the subrun information
124            cache->remove(cacheBlob);
125            cacheBlob.reset(SkRef(cache->createCachedBlob(blob, key, blurRec, skPaint)));
126            RegenerateTextBlob(cacheBlob, context->getBatchFontCache(),
127                               *context->caps()->shaderCaps(), skPaint, grPaint.getColor(),
128                               viewMatrix, props,
129                               blob, x, y, drawFilter);
130        } else {
131            cache->makeMRU(cacheBlob);
132
133            if (CACHE_SANITY_CHECK) {
134                int glyphCount = 0;
135                int runCount = 0;
136                GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob);
137                SkAutoTUnref<GrAtlasTextBlob> sanityBlob(cache->createBlob(glyphCount, runCount));
138                sanityBlob->setupKey(key, blurRec, skPaint);
139                RegenerateTextBlob(sanityBlob, context->getBatchFontCache(),
140                                   *context->caps()->shaderCaps(), skPaint,
141                                   grPaint.getColor(), viewMatrix, props,
142                                   blob, x, y, drawFilter);
143                GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
144            }
145        }
146    } else {
147        if (canCache) {
148            cacheBlob.reset(SkRef(cache->createCachedBlob(blob, key, blurRec, skPaint)));
149        } else {
150            cacheBlob.reset(cache->createBlob(blob));
151        }
152        RegenerateTextBlob(cacheBlob, context->getBatchFontCache(),
153                           *context->caps()->shaderCaps(), skPaint, grPaint.getColor(),
154                           viewMatrix, props,
155                           blob, x, y, drawFilter);
156    }
157
158    cacheBlob->flushCached(context, dc, blob, props, fDistanceAdjustTable, skPaint,
159                           grPaint, drawFilter, clip, viewMatrix, clipBounds, x, y);
160}
161
162void GrAtlasTextContext::RegenerateTextBlob(GrAtlasTextBlob* cacheBlob,
163                                            GrBatchFontCache* fontCache,
164                                            const GrShaderCaps& shaderCaps,
165                                            const SkPaint& skPaint, GrColor color,
166                                            const SkMatrix& viewMatrix,
167                                            const SkSurfaceProps& props,
168                                            const SkTextBlob* blob, SkScalar x, SkScalar y,
169                                            SkDrawFilter* drawFilter) {
170    cacheBlob->initReusableBlob(color, viewMatrix, x, y);
171
172    // Regenerate textblob
173    SkPaint runPaint = skPaint;
174    SkTextBlobRunIterator it(blob);
175    for (int run = 0; !it.done(); it.next(), run++) {
176        int glyphCount = it.glyphCount();
177        size_t textLen = glyphCount * sizeof(uint16_t);
178        const SkPoint& offset = it.offset();
179        // applyFontToPaint() always overwrites the exact same attributes,
180        // so it is safe to not re-seed the paint for this reason.
181        it.applyFontToPaint(&runPaint);
182
183        if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
184            // A false return from filter() means we should abort the current draw.
185            runPaint = skPaint;
186            continue;
187        }
188
189        runPaint.setFlags(GrTextUtils::FilterTextFlags(props, runPaint));
190
191        cacheBlob->push_back_run(run);
192
193        if (GrTextUtils::CanDrawAsDistanceFields(runPaint, viewMatrix, props, shaderCaps)) {
194            switch (it.positioning()) {
195                case SkTextBlob::kDefault_Positioning: {
196                    GrTextUtils::DrawDFText(cacheBlob, run, fontCache,
197                                            props, runPaint, color, viewMatrix,
198                                            (const char *)it.glyphs(), textLen,
199                                            x + offset.x(), y + offset.y());
200                    break;
201                }
202                case SkTextBlob::kHorizontal_Positioning: {
203                    SkPoint dfOffset = SkPoint::Make(x, y + offset.y());
204                    GrTextUtils::DrawDFPosText(cacheBlob, run, fontCache,
205                                               props, runPaint, color, viewMatrix,
206                                               (const char*)it.glyphs(), textLen, it.pos(),
207                                               1, dfOffset);
208                    break;
209                }
210                case SkTextBlob::kFull_Positioning: {
211                    SkPoint dfOffset = SkPoint::Make(x, y);
212                    GrTextUtils::DrawDFPosText(cacheBlob, run,  fontCache,
213                                               props, runPaint, color, viewMatrix,
214                                               (const char*)it.glyphs(), textLen, it.pos(),
215                                               2, dfOffset);
216                    break;
217                }
218            }
219        } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) {
220            cacheBlob->setRunDrawAsPaths(run);
221        } else {
222            switch (it.positioning()) {
223                case SkTextBlob::kDefault_Positioning:
224                    GrTextUtils::DrawBmpText(cacheBlob, run, fontCache,
225                                             props, runPaint, color, viewMatrix,
226                                             (const char *)it.glyphs(), textLen,
227                                             x + offset.x(), y + offset.y());
228                    break;
229                case SkTextBlob::kHorizontal_Positioning:
230                    GrTextUtils::DrawBmpPosText(cacheBlob, run, fontCache,
231                                                props, runPaint, color, viewMatrix,
232                                                (const char*)it.glyphs(), textLen, it.pos(), 1,
233                                                SkPoint::Make(x, y + offset.y()));
234                    break;
235                case SkTextBlob::kFull_Positioning:
236                    GrTextUtils::DrawBmpPosText(cacheBlob, run, fontCache,
237                                                props, runPaint, color, viewMatrix,
238                                                (const char*)it.glyphs(), textLen, it.pos(), 2,
239                                                SkPoint::Make(x, y));
240                    break;
241            }
242        }
243
244        if (drawFilter) {
245            // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
246            runPaint = skPaint;
247        }
248    }
249}
250
251inline GrAtlasTextBlob*
252GrAtlasTextContext::CreateDrawTextBlob(GrTextBlobCache* blobCache,
253                                       GrBatchFontCache* fontCache,
254                                       const GrShaderCaps& shaderCaps,
255                                       const GrPaint& paint,
256                                       const SkPaint& skPaint,
257                                       const SkMatrix& viewMatrix,
258                                       const SkSurfaceProps& props,
259                                       const char text[], size_t byteLength,
260                                       SkScalar x, SkScalar y) {
261    int glyphCount = skPaint.countText(text, byteLength);
262
263    GrAtlasTextBlob* blob = blobCache->createBlob(glyphCount, 1);
264    blob->initThrowawayBlob(viewMatrix, x, y);
265
266    if (GrTextUtils::CanDrawAsDistanceFields(skPaint, viewMatrix, props, shaderCaps)) {
267        GrTextUtils::DrawDFText(blob, 0, fontCache, props,
268                                skPaint, paint.getColor(), viewMatrix, text,
269                                byteLength, x, y);
270    } else {
271        GrTextUtils::DrawBmpText(blob, 0, fontCache, props, skPaint,
272                                 paint.getColor(), viewMatrix, text, byteLength, x, y);
273    }
274    return blob;
275}
276
277inline GrAtlasTextBlob*
278GrAtlasTextContext::CreateDrawPosTextBlob(GrTextBlobCache* blobCache, GrBatchFontCache* fontCache,
279                                          const GrShaderCaps& shaderCaps, const GrPaint& paint,
280                                          const SkPaint& skPaint,
281                                          const SkMatrix& viewMatrix, const SkSurfaceProps& props,
282                                          const char text[], size_t byteLength,
283                                          const SkScalar pos[], int scalarsPerPosition,
284                                          const SkPoint& offset) {
285    int glyphCount = skPaint.countText(text, byteLength);
286
287    GrAtlasTextBlob* blob = blobCache->createBlob(glyphCount, 1);
288    blob->initThrowawayBlob(viewMatrix, offset.x(), offset.y());
289
290    if (GrTextUtils::CanDrawAsDistanceFields(skPaint, viewMatrix, props, shaderCaps)) {
291        GrTextUtils::DrawDFPosText(blob, 0, fontCache, props,
292                                   skPaint, paint.getColor(), viewMatrix, text,
293                                   byteLength, pos, scalarsPerPosition, offset);
294    } else {
295        GrTextUtils::DrawBmpPosText(blob, 0, fontCache, props, skPaint,
296                                    paint.getColor(), viewMatrix, text,
297                                    byteLength, pos, scalarsPerPosition, offset);
298    }
299    return blob;
300}
301
302void GrAtlasTextContext::drawText(GrContext* context,
303                                  GrDrawContext* dc,
304                                  const GrClip& clip,
305                                  const GrPaint& paint, const SkPaint& skPaint,
306                                  const SkMatrix& viewMatrix,
307                                  const SkSurfaceProps& props,
308                                  const char text[], size_t byteLength,
309                                  SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
310    if (context->abandoned()) {
311        return;
312    } else if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) {
313        SkAutoTUnref<GrAtlasTextBlob> blob(
314            CreateDrawTextBlob(context->getTextBlobCache(), context->getBatchFontCache(),
315                               *context->caps()->shaderCaps(),
316                               paint, skPaint,
317                               viewMatrix, props,
318                               text, byteLength, x, y));
319        blob->flushThrowaway(context, dc, props, fDistanceAdjustTable, skPaint, paint,
320                             clip, viewMatrix, regionClipBounds, x, y);
321        return;
322    }
323
324    // fall back to drawing as a path
325    GrTextUtils::DrawTextAsPath(context, dc, clip, skPaint, viewMatrix, text, byteLength, x, y,
326                                regionClipBounds);
327}
328
329void GrAtlasTextContext::drawPosText(GrContext* context,
330                                     GrDrawContext* dc,
331                                     const GrClip& clip,
332                                     const GrPaint& paint, const SkPaint& skPaint,
333                                     const SkMatrix& viewMatrix,
334                                     const SkSurfaceProps& props,
335                                     const char text[], size_t byteLength,
336                                     const SkScalar pos[], int scalarsPerPosition,
337                                     const SkPoint& offset, const SkIRect& regionClipBounds) {
338    if (context->abandoned()) {
339        return;
340    } else if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) {
341        SkAutoTUnref<GrAtlasTextBlob> blob(
342            CreateDrawPosTextBlob(context->getTextBlobCache(),
343                                  context->getBatchFontCache(),
344                                  *context->caps()->shaderCaps(),
345                                  paint, skPaint, viewMatrix, props,
346                                  text, byteLength,
347                                  pos, scalarsPerPosition,
348                                  offset));
349        blob->flushThrowaway(context, dc, props, fDistanceAdjustTable, skPaint, paint,
350                             clip, viewMatrix, regionClipBounds, offset.fX, offset.fY);
351        return;
352    }
353
354    // fall back to drawing as a path
355    GrTextUtils::DrawPosTextAsPath(context, dc, props, clip, skPaint, viewMatrix, text,
356                                   byteLength, pos, scalarsPerPosition, offset, regionClipBounds);
357}
358
359///////////////////////////////////////////////////////////////////////////////////////////////////
360
361#ifdef GR_TEST_UTILS
362
363DRAW_BATCH_TEST_DEFINE(TextBlobBatch) {
364    static uint32_t gContextID = SK_InvalidGenID;
365    static GrAtlasTextContext* gTextContext = nullptr;
366    static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
367
368    if (context->uniqueID() != gContextID) {
369        gContextID = context->uniqueID();
370        delete gTextContext;
371
372        gTextContext = GrAtlasTextContext::Create();
373    }
374
375    // Setup dummy SkPaint / GrPaint
376    GrColor color = GrRandomColor(random);
377    SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
378    SkPaint skPaint;
379    skPaint.setColor(color);
380    skPaint.setLCDRenderText(random->nextBool());
381    skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
382    skPaint.setSubpixelText(random->nextBool());
383
384    GrPaint grPaint;
385    if (!SkPaintToGrPaint(context, skPaint, viewMatrix, &grPaint)) {
386        SkFAIL("couldn't convert paint\n");
387    }
388
389    const char* text = "The quick brown fox jumps over the lazy dog.";
390    int textLen = (int)strlen(text);
391
392    // create some random x/y offsets, including negative offsets
393    static const int kMaxTrans = 1024;
394    int xPos = (random->nextU() % 2) * 2 - 1;
395    int yPos = (random->nextU() % 2) * 2 - 1;
396    int xInt = (random->nextU() % kMaxTrans) * xPos;
397    int yInt = (random->nextU() % kMaxTrans) * yPos;
398    SkScalar x = SkIntToScalar(xInt);
399    SkScalar y = SkIntToScalar(yInt);
400
401    // right now we don't handle textblobs, nor do we handle drawPosText.  Since we only
402    // intend to test the batch with this unit test, that is okay.
403    SkAutoTUnref<GrAtlasTextBlob> blob(
404        GrAtlasTextContext::CreateDrawTextBlob(context->getTextBlobCache(),
405                                               context->getBatchFontCache(),
406                                               *context->caps()->shaderCaps(), grPaint, skPaint,
407                                               viewMatrix,
408                                               gSurfaceProps, text,
409                                               static_cast<size_t>(textLen), x, y));
410
411    return blob->test_createBatch(textLen, 0, 0, viewMatrix, x, y, color, skPaint,
412                                  gSurfaceProps, gTextContext->dfAdjustTable(),
413                                  context->getBatchFontCache());
414}
415
416#endif
417