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