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 
GrAtlasTextContext()18 GrAtlasTextContext::GrAtlasTextContext()
19     : fDistanceAdjustTable(new GrDistanceFieldAdjustTable) {
20 }
21 
22 
Create()23 GrAtlasTextContext* GrAtlasTextContext::Create() {
24     return new GrAtlasTextContext();
25 }
26 
canDraw(const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const GrShaderCaps & shaderCaps)27 bool 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 
ComputeCanonicalColor(const SkPaint & paint,bool lcd)35 GrColor 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.
HasLCD(const SkTextBlob * blob)59 bool 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 
drawTextBlob(GrContext * context,GrDrawContext * dc,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter,const SkIRect & clipBounds)69 void 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 
RegenerateTextBlob(GrAtlasTextBlob * cacheBlob,GrBatchFontCache * fontCache,const GrShaderCaps & shaderCaps,const SkPaint & skPaint,GrColor color,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter)162 void 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 
251 inline GrAtlasTextBlob*
CreateDrawTextBlob(GrTextBlobCache * blobCache,GrBatchFontCache * fontCache,const GrShaderCaps & shaderCaps,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,SkScalar x,SkScalar y)252 GrAtlasTextContext::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 
277 inline GrAtlasTextBlob*
CreateDrawPosTextBlob(GrTextBlobCache * blobCache,GrBatchFontCache * fontCache,const GrShaderCaps & shaderCaps,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset)278 GrAtlasTextContext::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 
drawText(GrContext * context,GrDrawContext * dc,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & regionClipBounds)302 void 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 
drawPosText(GrContext * context,GrDrawContext * dc,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & regionClipBounds)329 void 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 
DRAW_BATCH_TEST_DEFINE(TextBlobBatch)363 DRAW_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