blob: b2de1cf1548426a88c2f1794cfa627cde3deeed9 [file] [log] [blame]
joshualitt1d89e8d2015-04-01 12:40:54 -07001/*
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 "GrAtlas.h"
10#include "GrBatch.h"
11#include "GrBatchFontCache.h"
12#include "GrBatchTarget.h"
13#include "GrDefaultGeoProcFactory.h"
14#include "GrDrawTarget.h"
15#include "GrFontScaler.h"
16#include "GrIndexBuffer.h"
17#include "GrStrokeInfo.h"
18#include "GrTexturePriv.h"
19
20#include "SkAutoKern.h"
21#include "SkColorPriv.h"
22#include "SkDraw.h"
23#include "SkDrawFilter.h"
24#include "SkDrawProcs.h"
25#include "SkGlyphCache.h"
26#include "SkGpuDevice.h"
27#include "SkGr.h"
28#include "SkPath.h"
29#include "SkRTConf.h"
30#include "SkStrokeRec.h"
31#include "SkTextBlob.h"
32#include "SkTextMapStateProc.h"
33
34#include "effects/GrBitmapTextGeoProc.h"
35#include "effects/GrSimpleTextureEffect.h"
36
37namespace {
38static const size_t kLCDTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
39
40// position + local coord
41static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
42
43static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16);
44
45static const int kVerticesPerGlyph = 4;
46static const int kIndicesPerGlyph = 6;
47
48static size_t get_vertex_stride(GrMaskFormat maskFormat) {
49 switch (maskFormat) {
50 case kA8_GrMaskFormat:
51 return kGrayTextVASize;
52 case kARGB_GrMaskFormat:
53 return kColorTextVASize;
54 default:
55 return kLCDTextVASize;
56 }
57}
58
59};
60
61// TODO
62// More tests
63// move to SkCache
64// handle textblobs where the whole run is larger than the cache size
65// TODO implement micro speedy hash map for fast refing of glyphs
66
joshualittdbd35932015-04-02 09:19:04 -070067GrAtlasTextContext::GrAtlasTextContext(GrContext* context,
68 SkGpuDevice* gpuDevice,
69 const SkDeviceProperties& properties)
joshualitt1d89e8d2015-04-01 12:40:54 -070070 : INHERITED(context, gpuDevice, properties) {
71 fCurrStrike = NULL;
72}
73
joshualittdbd35932015-04-02 09:19:04 -070074void GrAtlasTextContext::ClearCacheEntry(uint32_t key, BitmapTextBlob** blob) {
joshualitt1d89e8d2015-04-01 12:40:54 -070075 (*blob)->unref();
76}
77
joshualittdbd35932015-04-02 09:19:04 -070078GrAtlasTextContext::~GrAtlasTextContext() {
79 fCache.foreach(&GrAtlasTextContext::ClearCacheEntry);
joshualitt1d89e8d2015-04-01 12:40:54 -070080}
81
joshualittdbd35932015-04-02 09:19:04 -070082GrAtlasTextContext* GrAtlasTextContext::Create(GrContext* context,
83 SkGpuDevice* gpuDevice,
84 const SkDeviceProperties& props) {
85 return SkNEW_ARGS(GrAtlasTextContext, (context, gpuDevice, props));
joshualitt1d89e8d2015-04-01 12:40:54 -070086}
87
joshualittdbd35932015-04-02 09:19:04 -070088bool GrAtlasTextContext::canDraw(const GrRenderTarget*,
89 const GrClip&,
90 const GrPaint&,
91 const SkPaint& skPaint,
92 const SkMatrix& viewMatrix) {
joshualitt1d89e8d2015-04-01 12:40:54 -070093 return !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
94}
95
joshualittdbd35932015-04-02 09:19:04 -070096inline void GrAtlasTextContext::init(GrRenderTarget* rt, const GrClip& clip,
97 const GrPaint& paint, const SkPaint& skPaint,
98 const SkIRect& regionClipBounds) {
joshualitt1d89e8d2015-04-01 12:40:54 -070099 INHERITED::init(rt, clip, paint, skPaint, regionClipBounds);
100
101 fCurrStrike = NULL;
102}
103
joshualittdbd35932015-04-02 09:19:04 -0700104bool GrAtlasTextContext::MustRegenerateBlob(const BitmapTextBlob& blob, const SkPaint& paint,
105 const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700106 // We always regenerate blobs with patheffects or mask filters we could cache these
107 // TODO find some way to cache the maskfilter / patheffects on the textblob
108 return !blob.fViewMatrix.cheapEqualTo(viewMatrix) || blob.fX != x || blob.fY != y ||
109 paint.getMaskFilter() || paint.getPathEffect() || paint.getStyle() != blob.fStyle;
110}
111
112
joshualittdbd35932015-04-02 09:19:04 -0700113inline SkGlyphCache* GrAtlasTextContext::setupCache(BitmapTextBlob::Run* run,
114 const SkPaint& skPaint,
115 const SkMatrix& viewMatrix) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700116 skPaint.getScalerContextDescriptor(&run->fDescriptor, &fDeviceProperties, &viewMatrix, false);
117 run->fTypeface.reset(SkSafeRef(skPaint.getTypeface()));
118 return SkGlyphCache::DetachCache(run->fTypeface, run->fDescriptor.getDesc());
119}
120
joshualittdbd35932015-04-02 09:19:04 -0700121inline void GrAtlasTextContext::BlobGlyphCount(int* glyphCount, int* runCount,
122 const SkTextBlob* blob) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700123 SkTextBlob::RunIterator itCounter(blob);
124 for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
125 *glyphCount += itCounter.glyphCount();
126 }
127}
128
joshualittdbd35932015-04-02 09:19:04 -0700129GrAtlasTextContext::BitmapTextBlob* GrAtlasTextContext::CreateBlob(int glyphCount,
130 int runCount) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700131 // We allocate size for the BitmapTextBlob itself, plus size for the vertices array,
132 // and size for the glyphIds array.
133 SK_COMPILE_ASSERT(kGrayTextVASize >= kColorTextVASize && kGrayTextVASize >= kLCDTextVASize,
134 vertex_attribute_changed);
135 size_t verticesCount = glyphCount * kVerticesPerGlyph * kGrayTextVASize;
136 size_t length = sizeof(BitmapTextBlob) +
137 verticesCount +
138 glyphCount * sizeof(GrGlyph::PackedID) +
139 sizeof(BitmapTextBlob::Run) * runCount;
140
141 BitmapTextBlob* cacheBlob = SkNEW_PLACEMENT(sk_malloc_throw(length), BitmapTextBlob);
142
143 // setup offsets for vertices / glyphs
144 cacheBlob->fVertices = sizeof(BitmapTextBlob) + reinterpret_cast<unsigned char*>(cacheBlob);
145 cacheBlob->fGlyphIDs =
146 reinterpret_cast<GrGlyph::PackedID*>(cacheBlob->fVertices + verticesCount);
147 cacheBlob->fRuns = reinterpret_cast<BitmapTextBlob::Run*>(cacheBlob->fGlyphIDs + glyphCount);
148
149 // Initialize runs
150 for (int i = 0; i < runCount; i++) {
151 SkNEW_PLACEMENT(&cacheBlob->fRuns[i], BitmapTextBlob::Run);
152 }
153 cacheBlob->fRunCount = runCount;
154 return cacheBlob;
155}
156
joshualittdbd35932015-04-02 09:19:04 -0700157void GrAtlasTextContext::drawTextBlob(GrRenderTarget* rt, const GrClip& clip,
158 const SkPaint& skPaint, const SkMatrix& viewMatrix,
159 const SkTextBlob* blob, SkScalar x, SkScalar y,
160 SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700161 BitmapTextBlob* cacheBlob;
162 BitmapTextBlob** foundBlob = fCache.find(blob->uniqueID());
163 SkIRect clipRect;
164 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
165
166 if (foundBlob) {
167 cacheBlob = *foundBlob;
168 if (MustRegenerateBlob(*cacheBlob, skPaint, viewMatrix, x, y)) {
169 // We have to remake the blob because changes may invalidate our masks.
170 // TODO we could probably get away reuse most of the time if the pointer is unique,
171 // but we'd have to clear the subrun information
172 cacheBlob->unref();
173 int glyphCount = 0;
174 int runCount = 0;
175 BlobGlyphCount(&glyphCount, &runCount, blob);
176 cacheBlob = CreateBlob(glyphCount, runCount);
177 fCache.set(blob->uniqueID(), cacheBlob);
178 this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter,
179 clipRect);
180 }
181 } else {
182 int glyphCount = 0;
183 int runCount = 0;
184 BlobGlyphCount(&glyphCount, &runCount, blob);
185 cacheBlob = CreateBlob(glyphCount, runCount);
186 fCache.set(blob->uniqueID(), cacheBlob);
187 this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, clipRect);
188 }
189
190 // Though for the time being runs in the textblob can override the paint, they only touch font
191 // info.
192 GrPaint grPaint;
193 SkPaint2GrPaintShader(fContext, rt, skPaint, viewMatrix, true, &grPaint);
194
195 this->flush(fContext->getTextTarget(), cacheBlob, rt, grPaint, clip, viewMatrix,
196 fSkPaint.getAlpha());
197}
198
joshualittdbd35932015-04-02 09:19:04 -0700199void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob,
200 const SkPaint& skPaint, const SkMatrix& viewMatrix,
201 const SkTextBlob* blob, SkScalar x, SkScalar y,
202 SkDrawFilter* drawFilter, const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700203 cacheBlob->fViewMatrix = viewMatrix;
204 cacheBlob->fX = x;
205 cacheBlob->fY = y;
206 cacheBlob->fStyle = skPaint.getStyle();
207
208 // Regenerate textblob
209 SkPaint runPaint = skPaint;
210 SkTextBlob::RunIterator it(blob);
211 for (int run = 0; !it.done(); it.next(), run++) {
212 int glyphCount = it.glyphCount();
213 size_t textLen = glyphCount * sizeof(uint16_t);
214 const SkPoint& offset = it.offset();
215 // applyFontToPaint() always overwrites the exact same attributes,
216 // so it is safe to not re-seed the paint for this reason.
217 it.applyFontToPaint(&runPaint);
218
219 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
220 // A false return from filter() means we should abort the current draw.
221 runPaint = skPaint;
222 continue;
223 }
224
225 runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint));
226
227 SkGlyphCache* cache = this->setupCache(&cacheBlob->fRuns[run], runPaint, viewMatrix);
228
229 // setup vertex / glyphIndex for the new run
230 if (run > 0) {
231 PerSubRunInfo& newRun = cacheBlob->fRuns[run].fSubRunInfo.back();
232 PerSubRunInfo& lastRun = cacheBlob->fRuns[run - 1].fSubRunInfo.back();
233
234 newRun.fVertexStartIndex = lastRun.fVertexEndIndex;
235 newRun.fVertexEndIndex = lastRun.fVertexEndIndex;
236
237 newRun.fGlyphStartIndex = lastRun.fGlyphEndIndex;
238 newRun.fGlyphEndIndex = lastRun.fGlyphEndIndex;
239 }
240
241 switch (it.positioning()) {
242 case SkTextBlob::kDefault_Positioning:
243 this->internalDrawText(cacheBlob, run, cache, runPaint, viewMatrix,
244 (const char *)it.glyphs(), textLen,
245 x + offset.x(), y + offset.y(), clipRect);
246 break;
247 case SkTextBlob::kHorizontal_Positioning:
248 this->internalDrawPosText(cacheBlob, run, cache, runPaint, viewMatrix,
249 (const char*)it.glyphs(), textLen, it.pos(), 1,
250 SkPoint::Make(x, y + offset.y()), clipRect);
251 break;
252 case SkTextBlob::kFull_Positioning:
253 this->internalDrawPosText(cacheBlob, run, cache, runPaint, viewMatrix,
254 (const char*)it.glyphs(), textLen, it.pos(), 2,
255 SkPoint::Make(x, y), clipRect);
256 break;
257 }
258
259 if (drawFilter) {
260 // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
261 runPaint = skPaint;
262 }
263
264 SkGlyphCache::AttachCache(cache);
265 }
266}
267
joshualittdbd35932015-04-02 09:19:04 -0700268void GrAtlasTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip,
269 const GrPaint& paint, const SkPaint& skPaint,
270 const SkMatrix& viewMatrix,
271 const char text[], size_t byteLength,
272 SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700273 int glyphCount = skPaint.countText(text, byteLength);
274 SkAutoTUnref<BitmapTextBlob> blob(CreateBlob(glyphCount, 1));
275 blob->fViewMatrix = viewMatrix;
276 blob->fX = x;
277 blob->fY = y;
278 blob->fStyle = skPaint.getStyle();
279
280 SkIRect clipRect;
281 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
282
283 // setup cache
284 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, viewMatrix);
285 this->internalDrawText(blob, 0, cache, skPaint, viewMatrix, text, byteLength, x, y, clipRect);
286 SkGlyphCache::AttachCache(cache);
287
288 this->flush(fContext->getTextTarget(), blob, rt, paint, clip, viewMatrix, skPaint.getAlpha());
289}
290
joshualittdbd35932015-04-02 09:19:04 -0700291void GrAtlasTextContext::internalDrawText(BitmapTextBlob* blob, int runIndex,
292 SkGlyphCache* cache, const SkPaint& skPaint,
293 const SkMatrix& viewMatrix,
294 const char text[], size_t byteLength,
295 SkScalar x, SkScalar y, const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700296 SkASSERT(byteLength == 0 || text != NULL);
297
298 // nothing to draw
299 if (text == NULL || byteLength == 0) {
300 return;
301 }
302
303 fCurrStrike = NULL;
304 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
305
306 // Get GrFontScaler from cache
307 GrFontScaler* fontScaler = GetGrFontScaler(cache);
308
309 // transform our starting point
310 {
311 SkPoint loc;
312 viewMatrix.mapXY(x, y, &loc);
313 x = loc.fX;
314 y = loc.fY;
315 }
316
317 // need to measure first
318 if (skPaint.getTextAlign() != SkPaint::kLeft_Align) {
319 SkVector stopVector;
320 MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector);
321
322 SkScalar stopX = stopVector.fX;
323 SkScalar stopY = stopVector.fY;
324
325 if (skPaint.getTextAlign() == SkPaint::kCenter_Align) {
326 stopX = SkScalarHalf(stopX);
327 stopY = SkScalarHalf(stopY);
328 }
329 x -= stopX;
330 y -= stopY;
331 }
332
333 const char* stop = text + byteLength;
334
335 SkAutoKern autokern;
336
337 SkFixed fxMask = ~0;
338 SkFixed fyMask = ~0;
339 SkScalar halfSampleX, halfSampleY;
340 if (cache->isSubpixel()) {
341 halfSampleX = halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
342 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
343 if (kX_SkAxisAlignment == baseline) {
344 fyMask = 0;
345 halfSampleY = SK_ScalarHalf;
346 } else if (kY_SkAxisAlignment == baseline) {
347 fxMask = 0;
348 halfSampleX = SK_ScalarHalf;
349 }
350 } else {
351 halfSampleX = halfSampleY = SK_ScalarHalf;
352 }
353
354 Sk48Dot16 fx = SkScalarTo48Dot16(x + halfSampleX);
355 Sk48Dot16 fy = SkScalarTo48Dot16(y + halfSampleY);
356
357 while (text < stop) {
358 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
359
360 fx += autokern.adjust(glyph);
361
362 if (glyph.fWidth) {
363 this->appendGlyph(blob,
364 runIndex,
365 GrGlyph::Pack(glyph.getGlyphID(),
366 glyph.getSubXFixed(),
367 glyph.getSubYFixed(),
368 GrGlyph::kCoverage_MaskStyle),
369 Sk48Dot16FloorToInt(fx),
370 Sk48Dot16FloorToInt(fy),
joshualitteef5b3e2015-04-03 08:07:26 -0700371 skPaint.getColor(),
joshualitt1d89e8d2015-04-01 12:40:54 -0700372 fontScaler,
373 clipRect);
374 }
375
376 fx += glyph.fAdvanceX;
377 fy += glyph.fAdvanceY;
378 }
379}
380
joshualittdbd35932015-04-02 09:19:04 -0700381void GrAtlasTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
382 const GrPaint& paint, const SkPaint& skPaint,
383 const SkMatrix& viewMatrix,
384 const char text[], size_t byteLength,
385 const SkScalar pos[], int scalarsPerPosition,
386 const SkPoint& offset, const SkIRect& regionClipBounds) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700387 int glyphCount = skPaint.countText(text, byteLength);
388 SkAutoTUnref<BitmapTextBlob> blob(CreateBlob(glyphCount, 1));
389 blob->fStyle = skPaint.getStyle();
390 blob->fViewMatrix = viewMatrix;
391
392 SkIRect clipRect;
393 clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
394
395 // setup cache
396 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, viewMatrix);
397 this->internalDrawPosText(blob, 0, cache, skPaint, viewMatrix, text, byteLength, pos,
398 scalarsPerPosition, offset, clipRect);
399 SkGlyphCache::AttachCache(cache);
400
401 this->flush(fContext->getTextTarget(), blob, rt, paint, clip, viewMatrix, fSkPaint.getAlpha());
402}
403
joshualittdbd35932015-04-02 09:19:04 -0700404void GrAtlasTextContext::internalDrawPosText(BitmapTextBlob* blob, int runIndex,
405 SkGlyphCache* cache, const SkPaint& skPaint,
406 const SkMatrix& viewMatrix,
407 const char text[], size_t byteLength,
408 const SkScalar pos[], int scalarsPerPosition,
409 const SkPoint& offset, const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700410 SkASSERT(byteLength == 0 || text != NULL);
411 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
412
413 // nothing to draw
414 if (text == NULL || byteLength == 0) {
415 return;
416 }
417
418 fCurrStrike = NULL;
419 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
420
421 // Get GrFontScaler from cache
422 GrFontScaler* fontScaler = GetGrFontScaler(cache);
423
424 const char* stop = text + byteLength;
425 SkTextAlignProc alignProc(skPaint.getTextAlign());
426 SkTextMapStateProc tmsProc(viewMatrix, offset, scalarsPerPosition);
427
428 if (cache->isSubpixel()) {
429 // maybe we should skip the rounding if linearText is set
430 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
431
432 SkFixed fxMask = ~0;
433 SkFixed fyMask = ~0;
434 SkScalar halfSampleX = SkFixedToScalar(SkGlyph::kSubpixelRound);
435 SkScalar halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
436 if (kX_SkAxisAlignment == baseline) {
437 fyMask = 0;
438 halfSampleY = SK_ScalarHalf;
439 } else if (kY_SkAxisAlignment == baseline) {
440 fxMask = 0;
441 halfSampleX = SK_ScalarHalf;
442 }
443
444 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
445 while (text < stop) {
446 SkPoint tmsLoc;
447 tmsProc(pos, &tmsLoc);
448 Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + halfSampleX);
449 Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + halfSampleY);
450
451 const SkGlyph& glyph = glyphCacheProc(cache, &text,
452 fx & fxMask, fy & fyMask);
453
454 if (glyph.fWidth) {
455 this->appendGlyph(blob,
456 runIndex,
457 GrGlyph::Pack(glyph.getGlyphID(),
458 glyph.getSubXFixed(),
459 glyph.getSubYFixed(),
460 GrGlyph::kCoverage_MaskStyle),
461 Sk48Dot16FloorToInt(fx),
462 Sk48Dot16FloorToInt(fy),
joshualitteef5b3e2015-04-03 08:07:26 -0700463 skPaint.getColor(),
joshualitt1d89e8d2015-04-01 12:40:54 -0700464 fontScaler,
465 clipRect);
466 }
467 pos += scalarsPerPosition;
468 }
469 } else {
470 while (text < stop) {
471 const char* currentText = text;
472 const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
473
474 if (metricGlyph.fWidth) {
475 SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
476 SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
477 SkPoint tmsLoc;
478 tmsProc(pos, &tmsLoc);
479 SkPoint alignLoc;
480 alignProc(tmsLoc, metricGlyph, &alignLoc);
481
482 Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + halfSampleX);
483 Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + halfSampleY);
484
485 // have to call again, now that we've been "aligned"
486 const SkGlyph& glyph = glyphCacheProc(cache, &currentText,
487 fx & fxMask, fy & fyMask);
488 // the assumption is that the metrics haven't changed
489 SkASSERT(prevAdvX == glyph.fAdvanceX);
490 SkASSERT(prevAdvY == glyph.fAdvanceY);
491 SkASSERT(glyph.fWidth);
492
493 this->appendGlyph(blob,
494 runIndex,
495 GrGlyph::Pack(glyph.getGlyphID(),
496 glyph.getSubXFixed(),
497 glyph.getSubYFixed(),
498 GrGlyph::kCoverage_MaskStyle),
499 Sk48Dot16FloorToInt(fx),
500 Sk48Dot16FloorToInt(fy),
joshualitteef5b3e2015-04-03 08:07:26 -0700501 skPaint.getColor(),
joshualitt1d89e8d2015-04-01 12:40:54 -0700502 fontScaler,
503 clipRect);
504 }
505 pos += scalarsPerPosition;
506 }
507 }
508 } else { // not subpixel
509
510 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
511 while (text < stop) {
512 // the last 2 parameters are ignored
513 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
514
515 if (glyph.fWidth) {
516 SkPoint tmsLoc;
517 tmsProc(pos, &tmsLoc);
518
519 Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + SK_ScalarHalf); //halfSampleX;
520 Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + SK_ScalarHalf); //halfSampleY;
521 this->appendGlyph(blob,
522 runIndex,
523 GrGlyph::Pack(glyph.getGlyphID(),
524 glyph.getSubXFixed(),
525 glyph.getSubYFixed(),
526 GrGlyph::kCoverage_MaskStyle),
527 Sk48Dot16FloorToInt(fx),
528 Sk48Dot16FloorToInt(fy),
joshualitteef5b3e2015-04-03 08:07:26 -0700529 skPaint.getColor(),
joshualitt1d89e8d2015-04-01 12:40:54 -0700530 fontScaler,
531 clipRect);
532 }
533 pos += scalarsPerPosition;
534 }
535 } else {
536 while (text < stop) {
537 // the last 2 parameters are ignored
538 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
539
540 if (glyph.fWidth) {
541 SkPoint tmsLoc;
542 tmsProc(pos, &tmsLoc);
543
544 SkPoint alignLoc;
545 alignProc(tmsLoc, glyph, &alignLoc);
546
547 Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + SK_ScalarHalf); //halfSampleX;
548 Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + SK_ScalarHalf); //halfSampleY;
549 this->appendGlyph(blob,
550 runIndex,
551 GrGlyph::Pack(glyph.getGlyphID(),
552 glyph.getSubXFixed(),
553 glyph.getSubYFixed(),
554 GrGlyph::kCoverage_MaskStyle),
555 Sk48Dot16FloorToInt(fx),
556 Sk48Dot16FloorToInt(fy),
joshualitteef5b3e2015-04-03 08:07:26 -0700557 skPaint.getColor(),
joshualitt1d89e8d2015-04-01 12:40:54 -0700558 fontScaler,
559 clipRect);
560 }
561 pos += scalarsPerPosition;
562 }
563 }
564 }
565}
566
joshualittdbd35932015-04-02 09:19:04 -0700567void GrAtlasTextContext::appendGlyph(BitmapTextBlob* blob, int runIndex, GrGlyph::PackedID packed,
joshualitteef5b3e2015-04-03 08:07:26 -0700568 int vx, int vy, GrColor color, GrFontScaler* scaler,
joshualittdbd35932015-04-02 09:19:04 -0700569 const SkIRect& clipRect) {
joshualitt1d89e8d2015-04-01 12:40:54 -0700570 if (NULL == fCurrStrike) {
571 fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler);
572 }
573
574 GrGlyph* glyph = fCurrStrike->getGlyph(packed, scaler);
575 if (NULL == glyph || glyph->fBounds.isEmpty()) {
576 return;
577 }
578
579 int x = vx + glyph->fBounds.fLeft;
580 int y = vy + glyph->fBounds.fTop;
581
582 // keep them as ints until we've done the clip-test
583 int width = glyph->fBounds.width();
584 int height = glyph->fBounds.height();
585
586 // check if we clipped out
587 if (clipRect.quickReject(x, y, x + width, y + height)) {
588 return;
589 }
590
591 // If the glyph is too large we fall back to paths
592 if (fCurrStrike->glyphTooLargeForAtlas(glyph)) {
593 if (NULL == glyph->fPath) {
594 SkPath* path = SkNEW(SkPath);
595 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
596 // flag the glyph as being dead?
597 SkDELETE(path);
598 return;
599 }
600 glyph->fPath = path;
601 }
602 SkASSERT(glyph->fPath);
603 blob->fBigGlyphs.push_back(BitmapTextBlob::BigGlyph(*glyph->fPath, vx, vy));
604 return;
605 }
606
607 Run& run = blob->fRuns[runIndex];
608
609 GrMaskFormat format = glyph->fMaskFormat;
610
611 PerSubRunInfo* subRun = &run.fSubRunInfo.back();
612 if (run.fInitialized && subRun->fMaskFormat != format) {
613 PerSubRunInfo* newSubRun = &run.fSubRunInfo.push_back();
614 newSubRun->fGlyphStartIndex = subRun->fGlyphEndIndex;
615 newSubRun->fGlyphEndIndex = subRun->fGlyphEndIndex;
616
617 newSubRun->fVertexStartIndex = subRun->fVertexEndIndex;
618 newSubRun->fVertexEndIndex = subRun->fVertexEndIndex;
619
620 subRun = newSubRun;
621 }
622
623 run.fInitialized = true;
624 subRun->fMaskFormat = format;
625 blob->fGlyphIDs[subRun->fGlyphEndIndex] = packed;
626
627 size_t vertexStride = get_vertex_stride(format);
628
629 SkRect r;
630 r.fLeft = SkIntToScalar(x);
631 r.fTop = SkIntToScalar(y);
632 r.fRight = r.fLeft + SkIntToScalar(width);
633 r.fBottom = r.fTop + SkIntToScalar(height);
634
635 run.fVertexBounds.joinNonEmptyArg(r);
joshualitt1d89e8d2015-04-01 12:40:54 -0700636 run.fColor = color;
637
638 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices + subRun->fVertexEndIndex);
639
640 // V0
641 SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
642 position->set(r.fLeft, r.fTop);
643 if (kA8_GrMaskFormat == format) {
644 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
645 *colorPtr = color;
646 }
647 vertex += vertexStride;
648
649 // V1
650 position = reinterpret_cast<SkPoint*>(vertex);
651 position->set(r.fLeft, r.fBottom);
652 if (kA8_GrMaskFormat == format) {
653 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
654 *colorPtr = color;
655 }
656 vertex += vertexStride;
657
658 // V2
659 position = reinterpret_cast<SkPoint*>(vertex);
660 position->set(r.fRight, r.fBottom);
661 if (kA8_GrMaskFormat == format) {
662 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
663 *colorPtr = color;
664 }
665 vertex += vertexStride;
666
667 // V3
668 position = reinterpret_cast<SkPoint*>(vertex);
669 position->set(r.fRight, r.fTop);
670 if (kA8_GrMaskFormat == format) {
671 SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
672 *colorPtr = color;
673 }
674
675 subRun->fGlyphEndIndex++;
676 subRun->fVertexEndIndex += vertexStride * kVerticesPerGlyph;
677}
678
679class BitmapTextBatch : public GrBatch {
680public:
joshualittdbd35932015-04-02 09:19:04 -0700681 typedef GrAtlasTextContext::BitmapTextBlob Blob;
joshualitt1d89e8d2015-04-01 12:40:54 -0700682 typedef Blob::Run Run;
683 typedef Run::SubRunInfo TextInfo;
684 struct Geometry {
685 Geometry() {}
686 Geometry(const Geometry& geometry)
687 : fBlob(SkRef(geometry.fBlob.get()))
688 , fRun(geometry.fRun)
689 , fSubRun(geometry.fSubRun)
690 , fColor(geometry.fColor) {}
691 SkAutoTUnref<Blob> fBlob;
692 int fRun;
693 int fSubRun;
694 GrColor fColor;
695 };
696
697 static GrBatch* Create(const Geometry& geometry, GrColor color, GrMaskFormat maskFormat,
698 int glyphCount, GrBatchFontCache* fontCache) {
699 return SkNEW_ARGS(BitmapTextBatch, (geometry, color, maskFormat, glyphCount, fontCache));
700 }
701
702 const char* name() const override { return "BitmapTextBatch"; }
703
704 void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
705 if (kARGB_GrMaskFormat == fMaskFormat) {
706 out->setUnknownFourComponents();
707 } else {
708 out->setKnownFourComponents(fBatch.fColor);
709 }
710 }
711
712 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
713 if (kARGB_GrMaskFormat != fMaskFormat) {
714 if (GrPixelConfigIsAlphaOnly(fPixelConfig)) {
715 out->setUnknownSingleComponent();
716 } else if (GrPixelConfigIsOpaque(fPixelConfig)) {
717 out->setUnknownOpaqueFourComponents();
718 out->setUsingLCDCoverage();
719 } else {
720 out->setUnknownFourComponents();
721 out->setUsingLCDCoverage();
722 }
723 } else {
724 out->setKnownSingleComponent(0xff);
725 }
726 }
727
728 void initBatchTracker(const GrPipelineInfo& init) override {
729 // Handle any color overrides
730 if (init.fColorIgnored) {
731 fBatch.fColor = GrColor_ILLEGAL;
732 } else if (GrColor_ILLEGAL != init.fOverrideColor) {
733 fBatch.fColor = init.fOverrideColor;
734 }
735
736 // setup batch properties
737 fBatch.fColorIgnored = init.fColorIgnored;
738 fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
739 fBatch.fCoverageIgnored = init.fCoverageIgnored;
740 }
741
742 void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
743 // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
744 // TODO actually only invert if we don't have RGBA
745 SkMatrix localMatrix;
746 if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) {
747 SkDebugf("Cannot invert viewmatrix\n");
748 return;
749 }
750
751 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
joshualitteef5b3e2015-04-03 08:07:26 -0700752
joshualitt1d89e8d2015-04-01 12:40:54 -0700753 // This will be ignored in the non A8 case
754 bool opaqueVertexColors = GrColorIsOpaque(this->color());
755 SkAutoTUnref<const GrGeometryProcessor> gp(
756 GrBitmapTextGeoProc::Create(this->color(),
757 fFontCache->getTexture(fMaskFormat),
758 params,
759 fMaskFormat,
760 opaqueVertexColors,
761 localMatrix));
762
763 size_t vertexStride = gp->getVertexStride();
764 SkASSERT(vertexStride == get_vertex_stride(fMaskFormat));
765
766 this->initDraw(batchTarget, gp, pipeline);
767
768 int glyphCount = this->numGlyphs();
769 int instanceCount = fGeoData.count();
770 const GrVertexBuffer* vertexBuffer;
771 int firstVertex;
772
773 void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
774 glyphCount * kVerticesPerGlyph,
775 &vertexBuffer,
776 &firstVertex);
777 if (!vertices) {
778 SkDebugf("Could not allocate vertices\n");
779 return;
780 }
781
782 unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
783
784 // setup drawinfo
785 const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
786 int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
787
788 GrDrawTarget::DrawInfo drawInfo;
789 drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
790 drawInfo.setStartVertex(0);
791 drawInfo.setStartIndex(0);
792 drawInfo.setVerticesPerInstance(kVerticesPerGlyph);
793 drawInfo.setIndicesPerInstance(kIndicesPerGlyph);
794 drawInfo.adjustStartVertex(firstVertex);
795 drawInfo.setVertexBuffer(vertexBuffer);
796 drawInfo.setIndexBuffer(quadIndexBuffer);
797
798 int instancesToFlush = 0;
799 for (int i = 0; i < instanceCount; i++) {
800 Geometry& args = fGeoData[i];
801 Blob* blob = args.fBlob;
802 Run& run = blob->fRuns[args.fRun];
803 TextInfo& info = run.fSubRunInfo[args.fSubRun];
804
805 uint64_t currentAtlasGen = fFontCache->atlasGeneration(fMaskFormat);
806 bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen;
807 bool regenerateColors = kA8_GrMaskFormat == fMaskFormat && run.fColor != args.fColor;
808 int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex;
809
810 // We regenerate both texture coords and colors in the blob itself, and update the
811 // atlas generation. If we don't end up purging any unused plots, we can avoid
812 // regenerating the coords. We could take a finer grained approach to updating texture
813 // coords but its not clear if the extra bookkeeping would offset any gains.
814 // To avoid looping over the glyphs twice, we do one loop and conditionally update color
815 // or coords as needed. One final note, if we have to break a run for an atlas eviction
816 // then we can't really trust the atlas has all of the correct data. Atlas evictions
817 // should be pretty rare, so we just always regenerate in those cases
818 if (regenerateTextureCoords || regenerateColors) {
819 // first regenerate texture coordinates / colors if need be
820 const SkDescriptor* desc = NULL;
821 SkGlyphCache* cache = NULL;
822 GrFontScaler* scaler = NULL;
823 GrBatchTextStrike* strike = NULL;
824 bool brokenRun = false;
825 if (regenerateTextureCoords) {
826 desc = run.fDescriptor.getDesc();
827 cache = SkGlyphCache::DetachCache(run.fTypeface, desc);
828 scaler = GrTextContext::GetGrFontScaler(cache);
829 strike = fFontCache->getStrike(scaler);
830 }
831 for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) {
832 GrGlyph::PackedID glyphID = blob->fGlyphIDs[glyphIdx + info.fGlyphStartIndex];
833
834 if (regenerateTextureCoords) {
835 // Upload the glyph only if needed
836 GrGlyph* glyph = strike->getGlyph(glyphID, scaler);
837 SkASSERT(glyph);
838
839 if (!fFontCache->hasGlyph(glyph) &&
840 !strike->addGlyphToAtlas(batchTarget, glyph, scaler)) {
841 this->flush(batchTarget, &drawInfo, instancesToFlush,
842 maxInstancesPerDraw);
843 this->initDraw(batchTarget, gp, pipeline);
844 instancesToFlush = 0;
845 brokenRun = glyphIdx > 0;
846
847 SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(batchTarget, glyph,
848 scaler);
849 SkASSERT(success);
850 }
851
852 fFontCache->setGlyphRefToken(glyph, batchTarget->currentToken());
853
854 // Texture coords are the last vertex attribute so we get a pointer to the
855 // first one and then map with stride in regenerateTextureCoords
856 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
857 vertex += info.fVertexStartIndex;
858 vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
859 vertex += vertexStride - sizeof(SkIPoint16);
860
861 this->regenerateTextureCoords(glyph, vertex, vertexStride);
862 }
863
864 if (regenerateColors) {
865 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices);
866 vertex += info.fVertexStartIndex;
867 vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint);
868 this->regenerateColors(vertex, vertexStride, args.fColor);
869 }
870
871 instancesToFlush++;
872 }
873
874 if (regenerateTextureCoords) {
875 SkGlyphCache::AttachCache(cache);
876 info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration :
877 fFontCache->atlasGeneration(fMaskFormat);
878 }
879 } else {
880 instancesToFlush += glyphCount;
881 }
882
883 // now copy all vertices
884 size_t byteCount = info.fVertexEndIndex - info.fVertexStartIndex;
885 memcpy(currVertex, blob->fVertices + info.fVertexStartIndex, byteCount);
886
887 currVertex += byteCount;
888 }
889
890 this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw);
891 }
892
893 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
894
895private:
896 BitmapTextBatch(const Geometry& geometry, GrColor color, GrMaskFormat maskFormat,
897 int glyphCount, GrBatchFontCache* fontCache)
898 : fMaskFormat(maskFormat)
899 , fPixelConfig(fontCache->getPixelConfig(maskFormat))
900 , fFontCache(fontCache) {
901 this->initClassID<BitmapTextBatch>();
902 fGeoData.push_back(geometry);
903 fBatch.fColor = color;
904 fBatch.fViewMatrix = geometry.fBlob->fViewMatrix;
905 fBatch.fNumGlyphs = glyphCount;
906 }
907
908 void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride) {
909 int width = glyph->fBounds.width();
910 int height = glyph->fBounds.height();
911 int u0 = glyph->fAtlasLocation.fX;
912 int v0 = glyph->fAtlasLocation.fY;
913 int u1 = u0 + width;
914 int v1 = v0 + height;
915
916 // we assume texture coords are the last vertex attribute, this is a bit fragile.
917 // TODO pass in this offset or something
918 SkIPoint16* textureCoords;
919 // V0
920 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
921 textureCoords->set(u0, v0);
922 vertex += vertexStride;
923
924 // V1
925 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
926 textureCoords->set(u0, v1);
927 vertex += vertexStride;
928
929 // V2
930 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
931 textureCoords->set(u1, v1);
932 vertex += vertexStride;
933
934 // V3
935 textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
936 textureCoords->set(u1, v0);
937 }
938
939 void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) {
940 for (int i = 0; i < kVerticesPerGlyph; i++) {
941 SkColor* vcolor = reinterpret_cast<SkColor*>(vertex);
942 *vcolor = color;
943 vertex += vertexStride;
944 }
945 }
946
947 void initDraw(GrBatchTarget* batchTarget,
948 const GrGeometryProcessor* gp,
949 const GrPipeline* pipeline) {
950 batchTarget->initDraw(gp, pipeline);
951
952 // TODO remove this when batch is everywhere
953 GrPipelineInfo init;
954 init.fColorIgnored = fBatch.fColorIgnored;
955 init.fOverrideColor = GrColor_ILLEGAL;
956 init.fCoverageIgnored = fBatch.fCoverageIgnored;
957 init.fUsesLocalCoords = this->usesLocalCoords();
958 gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
959 }
960
961 void flush(GrBatchTarget* batchTarget,
962 GrDrawTarget::DrawInfo* drawInfo,
963 int instanceCount,
964 int maxInstancesPerDraw) {
965 while (instanceCount) {
966 drawInfo->setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
967 drawInfo->setVertexCount(drawInfo->instanceCount() * drawInfo->verticesPerInstance());
968 drawInfo->setIndexCount(drawInfo->instanceCount() * drawInfo->indicesPerInstance());
969
970 batchTarget->draw(*drawInfo);
971
972 drawInfo->setStartVertex(drawInfo->startVertex() + drawInfo->vertexCount());
973 instanceCount -= drawInfo->instanceCount();
974 }
975 }
976
977 GrColor color() const { return fBatch.fColor; }
978 const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
979 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
980 int numGlyphs() const { return fBatch.fNumGlyphs; }
981
982 bool onCombineIfPossible(GrBatch* t) override {
983 BitmapTextBatch* that = t->cast<BitmapTextBatch>();
984
985 if (this->fMaskFormat != that->fMaskFormat) {
986 return false;
987 }
988
989 if (this->fMaskFormat != kA8_GrMaskFormat && this->color() != that->color()) {
990 return false;
991 }
992
993 if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
994 return false;
995 }
996
997 fBatch.fNumGlyphs += that->numGlyphs();
998 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
999 return true;
1000 }
1001
1002 struct BatchTracker {
1003 GrColor fColor;
1004 SkMatrix fViewMatrix;
1005 bool fUsesLocalCoords;
1006 bool fColorIgnored;
1007 bool fCoverageIgnored;
1008 int fNumGlyphs;
1009 };
1010
1011 BatchTracker fBatch;
1012 SkSTArray<1, Geometry, true> fGeoData;
1013 GrMaskFormat fMaskFormat;
1014 GrPixelConfig fPixelConfig;
1015 GrBatchFontCache* fFontCache;
1016};
1017
joshualittdbd35932015-04-02 09:19:04 -07001018void GrAtlasTextContext::flush(GrDrawTarget* target, BitmapTextBlob* blob, GrRenderTarget* rt,
1019 const GrPaint& paint, const GrClip& clip,
1020 const SkMatrix& viewMatrix, int paintAlpha) {
joshualitt1d89e8d2015-04-01 12:40:54 -07001021 GrPipelineBuilder pipelineBuilder;
1022 pipelineBuilder.setFromPaint(paint, rt, clip);
1023
1024 GrColor color = paint.getColor();
1025 for (uint32_t run = 0; run < blob->fRunCount; run++) {
1026 for (int subRun = 0; subRun < blob->fRuns[run].fSubRunInfo.count(); subRun++) {
1027 PerSubRunInfo& info = blob->fRuns[run].fSubRunInfo[subRun];
1028 int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex;
1029 if (0 == glyphCount) {
1030 continue;
1031 }
1032
1033 GrMaskFormat format = info.fMaskFormat;
joshualitteef5b3e2015-04-03 08:07:26 -07001034 GrColor subRunColor = kARGB_GrMaskFormat == format ?
1035 SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha) :
1036 color;
joshualitt1d89e8d2015-04-01 12:40:54 -07001037
1038 BitmapTextBatch::Geometry geometry;
1039 geometry.fBlob.reset(SkRef(blob));
1040 geometry.fRun = run;
1041 geometry.fSubRun = subRun;
1042 geometry.fColor = color;
joshualitteef5b3e2015-04-03 08:07:26 -07001043 SkAutoTUnref<GrBatch> batch(BitmapTextBatch::Create(geometry, subRunColor, format,
1044 glyphCount,
joshualitt1d89e8d2015-04-01 12:40:54 -07001045 fContext->getBatchFontCache()));
1046
1047 target->drawBatch(&pipelineBuilder, batch, &blob->fRuns[run].fVertexBounds);
1048 }
1049 }
1050
1051 // Now flush big glyphs
1052 for (int i = 0; i < blob->fBigGlyphs.count(); i++) {
1053 BitmapTextBlob::BigGlyph& bigGlyph = blob->fBigGlyphs[i];
1054 SkMatrix translate;
1055 translate.setTranslate(SkIntToScalar(bigGlyph.fVx), SkIntToScalar(bigGlyph.fVy));
1056 SkPath tmpPath(bigGlyph.fPath);
1057 tmpPath.transform(translate);
1058 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
1059 fContext->drawPath(rt, clip, paint, SkMatrix::I(), tmpPath, strokeInfo);
1060 }
1061}