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