1 /*
2 * Copyright 2013 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
8 #include "GrBitmapTextContext.h"
9 #include "GrAtlas.h"
10 #include "GrDrawTarget.h"
11 #include "GrFontScaler.h"
12 #include "GrIndexBuffer.h"
13 #include "GrStrokeInfo.h"
14 #include "GrTextStrike.h"
15 #include "GrTextStrike_impl.h"
16 #include "SkColorPriv.h"
17 #include "SkPath.h"
18 #include "SkRTConf.h"
19 #include "SkStrokeRec.h"
20 #include "effects/GrCustomCoordsTextureEffect.h"
21
22 #include "SkAutoKern.h"
23 #include "SkDraw.h"
24 #include "SkDrawProcs.h"
25 #include "SkGlyphCache.h"
26 #include "SkGpuDevice.h"
27 #include "SkGr.h"
28 #include "SkTextMapStateProc.h"
29
30 SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
31 "Dump the contents of the font cache before every purge.");
32
33 static const int kGlyphCoordsNoColorAttributeIndex = 1;
34 static const int kGlyphCoordsWithColorAttributeIndex = 2;
35
36 namespace {
37 // position + texture coord
38 extern const GrVertexAttrib gTextVertexAttribs[] = {
39 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
40 {kVec2f_GrVertexAttribType, sizeof(SkPoint) , kEffect_GrVertexAttribBinding}
41 };
42
43 // position + color + texture coord
44 extern const GrVertexAttrib gTextVertexWithColorAttribs[] = {
45 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
46 {kVec4ub_GrVertexAttribType, sizeof(SkPoint), kColor_GrVertexAttribBinding},
47 {kVec2f_GrVertexAttribType, sizeof(SkPoint) + sizeof(GrColor), kEffect_GrVertexAttribBinding}
48 };
49
50 };
51
GrBitmapTextContext(GrContext * context,const SkDeviceProperties & properties)52 GrBitmapTextContext::GrBitmapTextContext(GrContext* context,
53 const SkDeviceProperties& properties)
54 : GrTextContext(context, properties) {
55 fStrike = NULL;
56
57 fCurrTexture = NULL;
58 fCurrVertex = 0;
59 fEffectTextureGenID = 0;
60
61 fVertices = NULL;
62 fMaxVertices = 0;
63
64 fVertexBounds.setLargestInverted();
65 }
66
~GrBitmapTextContext()67 GrBitmapTextContext::~GrBitmapTextContext() {
68 this->flushGlyphs();
69 }
70
canDraw(const SkPaint & paint)71 bool GrBitmapTextContext::canDraw(const SkPaint& paint) {
72 return !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix());
73 }
74
skcolor_to_grcolor_nopremultiply(SkColor c)75 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
76 unsigned r = SkColorGetR(c);
77 unsigned g = SkColorGetG(c);
78 unsigned b = SkColorGetB(c);
79 return GrColorPackRGBA(r, g, b, 0xff);
80 }
81
flushGlyphs()82 void GrBitmapTextContext::flushGlyphs() {
83 if (NULL == fDrawTarget) {
84 return;
85 }
86
87 GrDrawState* drawState = fDrawTarget->drawState();
88 GrDrawState::AutoRestoreEffects are(drawState);
89 drawState->setFromPaint(fPaint, SkMatrix::I(), fContext->getRenderTarget());
90
91 if (fCurrVertex > 0) {
92 // setup our sampler state for our text texture/atlas
93 SkASSERT(SkIsAlign4(fCurrVertex));
94 SkASSERT(fCurrTexture);
95 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kNone_FilterMode);
96
97 uint32_t textureGenID = fCurrTexture->getGenerationID();
98
99 if (textureGenID != fEffectTextureGenID) {
100 fCachedEffect.reset(GrCustomCoordsTextureEffect::Create(fCurrTexture, params));
101 fEffectTextureGenID = textureGenID;
102 }
103
104 // This effect could be stored with one of the cache objects (atlas?)
105 int coordsIdx = drawState->hasColorVertexAttribute() ? kGlyphCoordsWithColorAttributeIndex :
106 kGlyphCoordsNoColorAttributeIndex;
107 drawState->addCoverageEffect(fCachedEffect.get(), coordsIdx);
108 SkASSERT(NULL != fStrike);
109 switch (fStrike->getMaskFormat()) {
110 // Color bitmap text
111 case kARGB_GrMaskFormat:
112 SkASSERT(!drawState->hasColorVertexAttribute());
113 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
114 drawState->setColor(0xffffffff);
115 break;
116 // LCD text
117 case kA888_GrMaskFormat:
118 case kA565_GrMaskFormat: {
119 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
120 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
121 fPaint.numColorStages()) {
122 GrPrintf("LCD Text will not draw correctly.\n");
123 }
124 SkASSERT(!drawState->hasColorVertexAttribute());
125 // We don't use the GrPaint's color in this case because it's been premultiplied by
126 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
127 // the mask texture color. The end result is that we get
128 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
129 int a = SkColorGetA(fSkPaint.getColor());
130 // paintAlpha
131 drawState->setColor(SkColorSetARGB(a, a, a, a));
132 // paintColor
133 drawState->setBlendConstant(skcolor_to_grcolor_nopremultiply(fSkPaint.getColor()));
134 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
135 break;
136 }
137 // Grayscale/BW text
138 case kA8_GrMaskFormat:
139 // set back to normal in case we took LCD path previously.
140 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
141 //drawState->setColor(fPaint.getColor());
142 // We're using per-vertex color.
143 SkASSERT(drawState->hasColorVertexAttribute());
144 drawState->setColor(0xFFFFFFFF);
145 break;
146 default:
147 SkFAIL("Unexepected mask format.");
148 }
149 int nGlyphs = fCurrVertex / 4;
150 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
151 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
152 nGlyphs,
153 4, 6, &fVertexBounds);
154
155 fDrawTarget->resetVertexSource();
156 fVertices = NULL;
157 fMaxVertices = 0;
158 fCurrVertex = 0;
159 fVertexBounds.setLargestInverted();
160 SkSafeSetNull(fCurrTexture);
161 }
162 }
163
init(const GrPaint & paint,const SkPaint & skPaint)164 inline void GrBitmapTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
165 GrTextContext::init(paint, skPaint);
166
167 fStrike = NULL;
168
169 fCurrTexture = NULL;
170 fCurrVertex = 0;
171
172 fVertices = NULL;
173 fMaxVertices = 0;
174 }
175
finish()176 inline void GrBitmapTextContext::finish() {
177 this->flushGlyphs();
178
179 GrTextContext::finish();
180 }
181
drawText(const GrPaint & paint,const SkPaint & skPaint,const char text[],size_t byteLength,SkScalar x,SkScalar y)182 void GrBitmapTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
183 const char text[], size_t byteLength,
184 SkScalar x, SkScalar y) {
185 SkASSERT(byteLength == 0 || text != NULL);
186
187 // nothing to draw
188 if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
189 return;
190 }
191
192 this->init(paint, skPaint);
193
194 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
195
196 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, &fContext->getMatrix());
197 SkGlyphCache* cache = autoCache.getCache();
198 GrFontScaler* fontScaler = GetGrFontScaler(cache);
199
200 // transform our starting point
201 {
202 SkPoint loc;
203 fContext->getMatrix().mapXY(x, y, &loc);
204 x = loc.fX;
205 y = loc.fY;
206 }
207
208 // need to measure first
209 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
210 SkVector stop;
211
212 MeasureText(cache, glyphCacheProc, text, byteLength, &stop);
213
214 SkScalar stopX = stop.fX;
215 SkScalar stopY = stop.fY;
216
217 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
218 stopX = SkScalarHalf(stopX);
219 stopY = SkScalarHalf(stopY);
220 }
221 x -= stopX;
222 y -= stopY;
223 }
224
225 const char* stop = text + byteLength;
226
227 SkAutoKern autokern;
228
229 SkFixed fxMask = ~0;
230 SkFixed fyMask = ~0;
231 SkFixed halfSampleX, halfSampleY;
232 if (cache->isSubpixel()) {
233 halfSampleX = halfSampleY = (SK_FixedHalf >> SkGlyph::kSubBits);
234 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(fContext->getMatrix());
235 if (kX_SkAxisAlignment == baseline) {
236 fyMask = 0;
237 halfSampleY = SK_FixedHalf;
238 } else if (kY_SkAxisAlignment == baseline) {
239 fxMask = 0;
240 halfSampleX = SK_FixedHalf;
241 }
242 } else {
243 halfSampleX = halfSampleY = SK_FixedHalf;
244 }
245
246 SkFixed fx = SkScalarToFixed(x) + halfSampleX;
247 SkFixed fy = SkScalarToFixed(y) + halfSampleY;
248
249 GrContext::AutoMatrix autoMatrix;
250 autoMatrix.setIdentity(fContext, &fPaint);
251
252 while (text < stop) {
253 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
254
255 fx += autokern.adjust(glyph);
256
257 if (glyph.fWidth) {
258 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
259 glyph.getSubXFixed(),
260 glyph.getSubYFixed()),
261 SkFixedFloorToFixed(fx),
262 SkFixedFloorToFixed(fy),
263 fontScaler);
264 }
265
266 fx += glyph.fAdvanceX;
267 fy += glyph.fAdvanceY;
268 }
269
270 this->finish();
271 }
272
drawPosText(const GrPaint & paint,const SkPaint & skPaint,const char text[],size_t byteLength,const SkScalar pos[],SkScalar constY,int scalarsPerPosition)273 void GrBitmapTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
274 const char text[], size_t byteLength,
275 const SkScalar pos[], SkScalar constY,
276 int scalarsPerPosition) {
277 SkASSERT(byteLength == 0 || text != NULL);
278 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
279
280 // nothing to draw
281 if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
282 return;
283 }
284
285 this->init(paint, skPaint);
286
287 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
288
289 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, &fContext->getMatrix());
290 SkGlyphCache* cache = autoCache.getCache();
291 GrFontScaler* fontScaler = GetGrFontScaler(cache);
292
293 // store original matrix before we reset, so we can use it to transform positions
294 SkMatrix ctm = fContext->getMatrix();
295 GrContext::AutoMatrix autoMatrix;
296 autoMatrix.setIdentity(fContext, &fPaint);
297
298 const char* stop = text + byteLength;
299 SkTextAlignProc alignProc(fSkPaint.getTextAlign());
300 SkTextMapStateProc tmsProc(ctm, constY, scalarsPerPosition);
301 SkFixed halfSampleX = 0, halfSampleY = 0;
302
303 if (cache->isSubpixel()) {
304 // maybe we should skip the rounding if linearText is set
305 SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(ctm);
306
307 SkFixed fxMask = ~0;
308 SkFixed fyMask = ~0;
309 if (kX_SkAxisAlignment == baseline) {
310 fyMask = 0;
311 #ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX
312 halfSampleY = SK_FixedHalf;
313 #endif
314 } else if (kY_SkAxisAlignment == baseline) {
315 fxMask = 0;
316 #ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX
317 halfSampleX = SK_FixedHalf;
318 #endif
319 }
320
321 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
322 while (text < stop) {
323 SkPoint tmsLoc;
324 tmsProc(pos, &tmsLoc);
325 SkFixed fx = SkScalarToFixed(tmsLoc.fX) + halfSampleX;
326 SkFixed fy = SkScalarToFixed(tmsLoc.fY) + halfSampleY;
327
328 const SkGlyph& glyph = glyphCacheProc(cache, &text,
329 fx & fxMask, fy & fyMask);
330
331 if (glyph.fWidth) {
332 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
333 glyph.getSubXFixed(),
334 glyph.getSubYFixed()),
335 SkFixedFloorToFixed(fx),
336 SkFixedFloorToFixed(fy),
337 fontScaler);
338 }
339 pos += scalarsPerPosition;
340 }
341 } else {
342 while (text < stop) {
343 const char* currentText = text;
344 const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
345
346 if (metricGlyph.fWidth) {
347 SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
348 SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
349 SkPoint tmsLoc;
350 tmsProc(pos, &tmsLoc);
351 SkIPoint fixedLoc;
352 alignProc(tmsLoc, metricGlyph, &fixedLoc);
353
354 SkFixed fx = fixedLoc.fX + halfSampleX;
355 SkFixed fy = fixedLoc.fY + halfSampleY;
356
357 // have to call again, now that we've been "aligned"
358 const SkGlyph& glyph = glyphCacheProc(cache, ¤tText,
359 fx & fxMask, fy & fyMask);
360 // the assumption is that the metrics haven't changed
361 SkASSERT(prevAdvX == glyph.fAdvanceX);
362 SkASSERT(prevAdvY == glyph.fAdvanceY);
363 SkASSERT(glyph.fWidth);
364
365 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
366 glyph.getSubXFixed(),
367 glyph.getSubYFixed()),
368 SkFixedFloorToFixed(fx),
369 SkFixedFloorToFixed(fy),
370 fontScaler);
371 }
372 pos += scalarsPerPosition;
373 }
374 }
375 } else { // not subpixel
376
377 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
378 while (text < stop) {
379 // the last 2 parameters are ignored
380 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
381
382 if (glyph.fWidth) {
383 SkPoint tmsLoc;
384 tmsProc(pos, &tmsLoc);
385
386 SkFixed fx = SkScalarToFixed(tmsLoc.fX) + SK_FixedHalf; //halfSampleX;
387 SkFixed fy = SkScalarToFixed(tmsLoc.fY) + SK_FixedHalf; //halfSampleY;
388 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
389 glyph.getSubXFixed(),
390 glyph.getSubYFixed()),
391 SkFixedFloorToFixed(fx),
392 SkFixedFloorToFixed(fy),
393 fontScaler);
394 }
395 pos += scalarsPerPosition;
396 }
397 } else {
398 while (text < stop) {
399 // the last 2 parameters are ignored
400 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
401
402 if (glyph.fWidth) {
403 SkPoint tmsLoc;
404 tmsProc(pos, &tmsLoc);
405
406 SkIPoint fixedLoc;
407 alignProc(tmsLoc, glyph, &fixedLoc);
408
409 SkFixed fx = fixedLoc.fX + SK_FixedHalf; //halfSampleX;
410 SkFixed fy = fixedLoc.fY + SK_FixedHalf; //halfSampleY;
411 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
412 glyph.getSubXFixed(),
413 glyph.getSubYFixed()),
414 SkFixedFloorToFixed(fx),
415 SkFixedFloorToFixed(fy),
416 fontScaler);
417 }
418 pos += scalarsPerPosition;
419 }
420 }
421 }
422
423 this->finish();
424 }
425
drawPackedGlyph(GrGlyph::PackedID packed,SkFixed vx,SkFixed vy,GrFontScaler * scaler)426 void GrBitmapTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
427 SkFixed vx, SkFixed vy,
428 GrFontScaler* scaler) {
429 if (NULL == fDrawTarget) {
430 return;
431 }
432
433 if (NULL == fStrike) {
434 fStrike = fContext->getFontCache()->getStrike(scaler, false);
435 }
436
437 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
438 if (NULL == glyph || glyph->fBounds.isEmpty()) {
439 return;
440 }
441
442 vx += SkIntToFixed(glyph->fBounds.fLeft);
443 vy += SkIntToFixed(glyph->fBounds.fTop);
444
445 // keep them as ints until we've done the clip-test
446 SkFixed width = glyph->fBounds.width();
447 SkFixed height = glyph->fBounds.height();
448
449 // check if we clipped out
450 if (true || NULL == glyph->fPlot) {
451 int x = vx >> 16;
452 int y = vy >> 16;
453 if (fClipRect.quickReject(x, y, x + width, y + height)) {
454 // SkCLZ(3); // so we can set a break-point in the debugger
455 return;
456 }
457 }
458
459 if (NULL == glyph->fPlot) {
460 if (fStrike->addGlyphToAtlas(glyph, scaler)) {
461 goto HAS_ATLAS;
462 }
463
464 // try to clear out an unused plot before we flush
465 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
466 fStrike->addGlyphToAtlas(glyph, scaler)) {
467 goto HAS_ATLAS;
468 }
469
470 if (c_DumpFontCache) {
471 #ifdef SK_DEVELOPER
472 fContext->getFontCache()->dump();
473 #endif
474 }
475
476 // flush any accumulated draws to allow us to free up a plot
477 this->flushGlyphs();
478 fContext->flush();
479
480 // we should have an unused plot now
481 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
482 fStrike->addGlyphToAtlas(glyph, scaler)) {
483 goto HAS_ATLAS;
484 }
485
486 if (NULL == glyph->fPath) {
487 SkPath* path = SkNEW(SkPath);
488 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
489 // flag the glyph as being dead?
490 delete path;
491 return;
492 }
493 glyph->fPath = path;
494 }
495
496 GrContext::AutoMatrix am;
497 SkMatrix translate;
498 translate.setTranslate(SkFixedToScalar(vx - SkIntToFixed(glyph->fBounds.fLeft)),
499 SkFixedToScalar(vy - SkIntToFixed(glyph->fBounds.fTop)));
500 GrPaint tmpPaint(fPaint);
501 am.setPreConcat(fContext, translate, &tmpPaint);
502 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
503 fContext->drawPath(tmpPaint, *glyph->fPath, strokeInfo);
504 return;
505 }
506
507 HAS_ATLAS:
508 SkASSERT(glyph->fPlot);
509 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
510 glyph->fPlot->setDrawToken(drawToken);
511
512 // now promote them to fixed (TODO: Rethink using fixed pt).
513 width = SkIntToFixed(width);
514 height = SkIntToFixed(height);
515
516 GrTexture* texture = glyph->fPlot->texture();
517 SkASSERT(texture);
518
519 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
520 this->flushGlyphs();
521 fCurrTexture = texture;
522 fCurrTexture->ref();
523 }
524
525 bool useColorVerts = kA8_GrMaskFormat == fStrike->getMaskFormat();
526
527 if (NULL == fVertices) {
528 // If we need to reserve vertices allow the draw target to suggest
529 // a number of verts to reserve and whether to perform a flush.
530 fMaxVertices = kMinRequestedVerts;
531 if (useColorVerts) {
532 fDrawTarget->drawState()->setVertexAttribs<gTextVertexWithColorAttribs>(
533 SK_ARRAY_COUNT(gTextVertexWithColorAttribs));
534 } else {
535 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
536 SK_ARRAY_COUNT(gTextVertexAttribs));
537 }
538 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
539 if (flush) {
540 this->flushGlyphs();
541 fContext->flush();
542 if (useColorVerts) {
543 fDrawTarget->drawState()->setVertexAttribs<gTextVertexWithColorAttribs>(
544 SK_ARRAY_COUNT(gTextVertexWithColorAttribs));
545 } else {
546 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
547 SK_ARRAY_COUNT(gTextVertexAttribs));
548 }
549 }
550 fMaxVertices = kDefaultRequestedVerts;
551 // ignore return, no point in flushing again.
552 fDrawTarget->geometryHints(&fMaxVertices, NULL);
553
554 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
555 if (fMaxVertices < kMinRequestedVerts) {
556 fMaxVertices = kDefaultRequestedVerts;
557 } else if (fMaxVertices > maxQuadVertices) {
558 // don't exceed the limit of the index buffer
559 fMaxVertices = maxQuadVertices;
560 }
561 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
562 0,
563 &fVertices,
564 NULL);
565 GrAlwaysAssert(success);
566 }
567
568 SkFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX);
569 SkFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY);
570
571 SkRect r;
572 r.fLeft = SkFixedToFloat(vx);
573 r.fTop = SkFixedToFloat(vy);
574 r.fRight = SkFixedToFloat(vx + width);
575 r.fBottom = SkFixedToFloat(vy + height);
576
577 fVertexBounds.growToInclude(r);
578
579 size_t vertSize = useColorVerts ? (2 * sizeof(SkPoint) + sizeof(GrColor)) :
580 (2 * sizeof(SkPoint));
581
582 SkASSERT(vertSize == fDrawTarget->getDrawState().getVertexSize());
583
584 SkPoint* positions = reinterpret_cast<SkPoint*>(
585 reinterpret_cast<intptr_t>(fVertices) + vertSize * fCurrVertex);
586 positions->setRectFan(r.fLeft, r.fTop, r.fRight, r.fBottom, vertSize);
587
588 // The texture coords are last in both the with and without color vertex layouts.
589 SkPoint* textureCoords = reinterpret_cast<SkPoint*>(
590 reinterpret_cast<intptr_t>(positions) + vertSize - sizeof(SkPoint));
591 textureCoords->setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
592 SkFixedToFloat(texture->normalizeFixedY(ty)),
593 SkFixedToFloat(texture->normalizeFixedX(tx + width)),
594 SkFixedToFloat(texture->normalizeFixedY(ty + height)),
595 vertSize);
596 if (useColorVerts) {
597 // color comes after position.
598 GrColor* colors = reinterpret_cast<GrColor*>(positions + 1);
599 for (int i = 0; i < 4; ++i) {
600 *colors = fPaint.getColor();
601 colors = reinterpret_cast<GrColor*>(reinterpret_cast<intptr_t>(colors) + vertSize);
602 }
603 }
604 fCurrVertex += 4;
605 }
606