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
30SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
31                "Dump the contents of the font cache before every purge.");
32
33static const int kGlyphCoordsNoColorAttributeIndex = 1;
34static const int kGlyphCoordsWithColorAttributeIndex = 2;
35
36namespace {
37// position + texture coord
38extern const GrVertexAttrib gTextVertexAttribs[] = {
39    {kVec2f_GrVertexAttribType, 0,               kPosition_GrVertexAttribBinding},
40    {kVec2f_GrVertexAttribType, sizeof(SkPoint) , kEffect_GrVertexAttribBinding}
41};
42
43// position + color + texture coord
44extern 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
52GrBitmapTextContext::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
67GrBitmapTextContext::~GrBitmapTextContext() {
68    this->flushGlyphs();
69}
70
71bool GrBitmapTextContext::canDraw(const SkPaint& paint) {
72    return !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix());
73}
74
75static 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
82void 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
164inline 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
176inline void GrBitmapTextContext::finish() {
177    this->flushGlyphs();
178
179    GrTextContext::finish();
180}
181
182void 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
273void 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, &currentText,
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
426void 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
507HAS_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