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