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