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
33namespace {
34// position + texture coord
35extern const GrVertexAttrib gTextVertexAttribs[] = {
36    {kVec2f_GrVertexAttribType, 0,               kPosition_GrVertexAttribBinding},
37    {kVec2f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding}
38};
39
40static const size_t kTextVASize = 2 * sizeof(SkPoint);
41
42// position + color + texture coord
43extern const GrVertexAttrib gTextVertexWithColorAttribs[] = {
44    {kVec2f_GrVertexAttribType,  0,                                 kPosition_GrVertexAttribBinding},
45    {kVec4ub_GrVertexAttribType, sizeof(SkPoint),                   kColor_GrVertexAttribBinding},
46    {kVec2f_GrVertexAttribType,  sizeof(SkPoint) + sizeof(GrColor), kGeometryProcessor_GrVertexAttribBinding}
47};
48
49static const size_t kTextVAColorSize = 2 * sizeof(SkPoint) + sizeof(GrColor);
50
51};
52
53GrBitmapTextContext::GrBitmapTextContext(GrContext* context,
54                                         const SkDeviceProperties& properties)
55                                       : GrTextContext(context, properties) {
56    fStrike = NULL;
57
58    fCurrTexture = NULL;
59    fCurrVertex = 0;
60    fEffectTextureUniqueID = SK_InvalidUniqueID;
61
62    fVertices = NULL;
63    fMaxVertices = 0;
64
65    fVertexBounds.setLargestInverted();
66}
67
68GrBitmapTextContext::~GrBitmapTextContext() {
69    this->flushGlyphs();
70}
71
72bool GrBitmapTextContext::canDraw(const SkPaint& paint) {
73    return !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix());
74}
75
76static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
77    unsigned r = SkColorGetR(c);
78    unsigned g = SkColorGetG(c);
79    unsigned b = SkColorGetB(c);
80    return GrColorPackRGBA(r, g, b, 0xff);
81}
82
83void GrBitmapTextContext::flushGlyphs() {
84    if (NULL == fDrawTarget) {
85        return;
86    }
87
88    GrDrawState* drawState = fDrawTarget->drawState();
89    GrDrawState::AutoRestoreEffects are(drawState);
90    drawState->setFromPaint(fPaint, SkMatrix::I(), fContext->getRenderTarget());
91
92    if (fCurrVertex > 0) {
93        // setup our sampler state for our text texture/atlas
94        SkASSERT(SkIsAlign4(fCurrVertex));
95        SkASSERT(fCurrTexture);
96        GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kNone_FilterMode);
97
98        uint32_t textureUniqueID = fCurrTexture->getUniqueID();
99
100        if (textureUniqueID != fEffectTextureUniqueID) {
101            fCachedGeometryProcessor.reset(GrCustomCoordsTextureEffect::Create(fCurrTexture,
102                                                                               params));
103            fEffectTextureUniqueID = textureUniqueID;
104        }
105
106        // This effect could be stored with one of the cache objects (atlas?)
107        drawState->setGeometryProcessor(fCachedGeometryProcessor.get());
108        SkASSERT(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                // We're using per-vertex color.
142                SkASSERT(drawState->hasColorVertexAttribute());
143                break;
144            default:
145                SkFAIL("Unexepected mask format.");
146        }
147        int nGlyphs = fCurrVertex / 4;
148        fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
149        fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
150                                          nGlyphs,
151                                          4, 6, &fVertexBounds);
152
153        fDrawTarget->resetVertexSource();
154        fVertices = NULL;
155        fMaxVertices = 0;
156        fCurrVertex = 0;
157        fVertexBounds.setLargestInverted();
158        SkSafeSetNull(fCurrTexture);
159    }
160}
161
162inline void GrBitmapTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
163    GrTextContext::init(paint, skPaint);
164
165    fStrike = NULL;
166
167    fCurrTexture = NULL;
168    fCurrVertex = 0;
169
170    fVertices = NULL;
171    fMaxVertices = 0;
172}
173
174inline void GrBitmapTextContext::finish() {
175    this->flushGlyphs();
176
177    GrTextContext::finish();
178}
179
180void GrBitmapTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
181                                   const char text[], size_t byteLength,
182                                   SkScalar x, SkScalar y) {
183    SkASSERT(byteLength == 0 || text != NULL);
184
185    // nothing to draw
186    if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
187        return;
188    }
189
190    this->init(paint, skPaint);
191
192    SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
193
194    SkAutoGlyphCache    autoCache(fSkPaint, &fDeviceProperties, &fContext->getMatrix());
195    SkGlyphCache*       cache = autoCache.getCache();
196    GrFontScaler*       fontScaler = GetGrFontScaler(cache);
197
198    // transform our starting point
199    {
200        SkPoint loc;
201        fContext->getMatrix().mapXY(x, y, &loc);
202        x = loc.fX;
203        y = loc.fY;
204    }
205
206    // need to measure first
207    if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
208        SkVector    stop;
209
210        MeasureText(cache, glyphCacheProc, text, byteLength, &stop);
211
212        SkScalar    stopX = stop.fX;
213        SkScalar    stopY = stop.fY;
214
215        if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
216            stopX = SkScalarHalf(stopX);
217            stopY = SkScalarHalf(stopY);
218        }
219        x -= stopX;
220        y -= stopY;
221    }
222
223    const char* stop = text + byteLength;
224
225    SkAutoKern autokern;
226
227    SkFixed fxMask = ~0;
228    SkFixed fyMask = ~0;
229    SkFixed halfSampleX, halfSampleY;
230    if (cache->isSubpixel()) {
231        halfSampleX = halfSampleY = (SK_FixedHalf >> SkGlyph::kSubBits);
232        SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(fContext->getMatrix());
233        if (kX_SkAxisAlignment == baseline) {
234            fyMask = 0;
235            halfSampleY = SK_FixedHalf;
236        } else if (kY_SkAxisAlignment == baseline) {
237            fxMask = 0;
238            halfSampleX = SK_FixedHalf;
239        }
240    } else {
241        halfSampleX = halfSampleY = SK_FixedHalf;
242    }
243
244    SkFixed fx = SkScalarToFixed(x) + halfSampleX;
245    SkFixed fy = SkScalarToFixed(y) + halfSampleY;
246
247    GrContext::AutoMatrix  autoMatrix;
248    autoMatrix.setIdentity(fContext, &fPaint);
249
250    while (text < stop) {
251        const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
252
253        fx += autokern.adjust(glyph);
254
255        if (glyph.fWidth) {
256            this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
257                                          glyph.getSubXFixed(),
258                                          glyph.getSubYFixed()),
259                                  SkFixedFloorToFixed(fx),
260                                  SkFixedFloorToFixed(fy),
261                                  fontScaler);
262        }
263
264        fx += glyph.fAdvanceX;
265        fy += glyph.fAdvanceY;
266    }
267
268    this->finish();
269}
270
271void GrBitmapTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
272                                      const char text[], size_t byteLength,
273                                      const SkScalar pos[], SkScalar constY,
274                                      int scalarsPerPosition) {
275    SkASSERT(byteLength == 0 || text != NULL);
276    SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
277
278    // nothing to draw
279    if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
280        return;
281    }
282
283    this->init(paint, skPaint);
284
285    SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
286
287    SkAutoGlyphCache    autoCache(fSkPaint, &fDeviceProperties, &fContext->getMatrix());
288    SkGlyphCache*       cache = autoCache.getCache();
289    GrFontScaler*       fontScaler = GetGrFontScaler(cache);
290
291    // store original matrix before we reset, so we can use it to transform positions
292    SkMatrix ctm = fContext->getMatrix();
293    GrContext::AutoMatrix  autoMatrix;
294    autoMatrix.setIdentity(fContext, &fPaint);
295
296    const char*        stop = text + byteLength;
297    SkTextAlignProc    alignProc(fSkPaint.getTextAlign());
298    SkTextMapStateProc tmsProc(ctm, constY, scalarsPerPosition);
299    SkFixed halfSampleX = 0, halfSampleY = 0;
300
301    if (cache->isSubpixel()) {
302        // maybe we should skip the rounding if linearText is set
303        SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(ctm);
304
305        SkFixed fxMask = ~0;
306        SkFixed fyMask = ~0;
307        if (kX_SkAxisAlignment == baseline) {
308            fyMask = 0;
309#ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX
310            halfSampleY = SK_FixedHalf;
311#endif
312        } else if (kY_SkAxisAlignment == baseline) {
313            fxMask = 0;
314#ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX
315            halfSampleX = SK_FixedHalf;
316#endif
317        }
318
319        if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
320            while (text < stop) {
321                SkPoint tmsLoc;
322                tmsProc(pos, &tmsLoc);
323                SkFixed fx = SkScalarToFixed(tmsLoc.fX) + halfSampleX;
324                SkFixed fy = SkScalarToFixed(tmsLoc.fY) + halfSampleY;
325
326                const SkGlyph& glyph = glyphCacheProc(cache, &text,
327                                                      fx & fxMask, fy & fyMask);
328
329                if (glyph.fWidth) {
330                    this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
331                                                        glyph.getSubXFixed(),
332                                                        glyph.getSubYFixed()),
333                                          SkFixedFloorToFixed(fx),
334                                          SkFixedFloorToFixed(fy),
335                                          fontScaler);
336                }
337                pos += scalarsPerPosition;
338            }
339        } else {
340            while (text < stop) {
341                const char* currentText = text;
342                const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
343
344                if (metricGlyph.fWidth) {
345                    SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
346                    SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
347                    SkPoint tmsLoc;
348                    tmsProc(pos, &tmsLoc);
349                    SkIPoint fixedLoc;
350                    alignProc(tmsLoc, metricGlyph, &fixedLoc);
351
352                    SkFixed fx = fixedLoc.fX + halfSampleX;
353                    SkFixed fy = fixedLoc.fY + halfSampleY;
354
355                    // have to call again, now that we've been "aligned"
356                    const SkGlyph& glyph = glyphCacheProc(cache, &currentText,
357                                                          fx & fxMask, fy & fyMask);
358                    // the assumption is that the metrics haven't changed
359                    SkASSERT(prevAdvX == glyph.fAdvanceX);
360                    SkASSERT(prevAdvY == glyph.fAdvanceY);
361                    SkASSERT(glyph.fWidth);
362
363                    this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
364                                                        glyph.getSubXFixed(),
365                                                        glyph.getSubYFixed()),
366                                          SkFixedFloorToFixed(fx),
367                                          SkFixedFloorToFixed(fy),
368                                          fontScaler);
369                }
370                pos += scalarsPerPosition;
371            }
372        }
373    } else {    // not subpixel
374
375        if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
376            while (text < stop) {
377                // the last 2 parameters are ignored
378                const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
379
380                if (glyph.fWidth) {
381                    SkPoint tmsLoc;
382                    tmsProc(pos, &tmsLoc);
383
384                    SkFixed fx = SkScalarToFixed(tmsLoc.fX) + SK_FixedHalf; //halfSampleX;
385                    SkFixed fy = SkScalarToFixed(tmsLoc.fY) + SK_FixedHalf; //halfSampleY;
386                    this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
387                                                        glyph.getSubXFixed(),
388                                                        glyph.getSubYFixed()),
389                                          SkFixedFloorToFixed(fx),
390                                          SkFixedFloorToFixed(fy),
391                                          fontScaler);
392                }
393                pos += scalarsPerPosition;
394            }
395        } else {
396            while (text < stop) {
397                // the last 2 parameters are ignored
398                const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
399
400                if (glyph.fWidth) {
401                    SkPoint tmsLoc;
402                    tmsProc(pos, &tmsLoc);
403
404                    SkIPoint fixedLoc;
405                    alignProc(tmsLoc, glyph, &fixedLoc);
406
407                    SkFixed fx = fixedLoc.fX + SK_FixedHalf; //halfSampleX;
408                    SkFixed fy = fixedLoc.fY + SK_FixedHalf; //halfSampleY;
409                    this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
410                                                        glyph.getSubXFixed(),
411                                                        glyph.getSubYFixed()),
412                                          SkFixedFloorToFixed(fx),
413                                          SkFixedFloorToFixed(fy),
414                                          fontScaler);
415                }
416                pos += scalarsPerPosition;
417            }
418        }
419    }
420
421    this->finish();
422}
423
424void GrBitmapTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
425                                          SkFixed vx, SkFixed vy,
426                                          GrFontScaler* scaler) {
427    if (NULL == fDrawTarget) {
428        return;
429    }
430
431    if (NULL == fStrike) {
432        fStrike = fContext->getFontCache()->getStrike(scaler, false);
433    }
434
435    GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
436    if (NULL == glyph || glyph->fBounds.isEmpty()) {
437        return;
438    }
439
440    vx += SkIntToFixed(glyph->fBounds.fLeft);
441    vy += SkIntToFixed(glyph->fBounds.fTop);
442
443    // keep them as ints until we've done the clip-test
444    SkFixed width = glyph->fBounds.width();
445    SkFixed height = glyph->fBounds.height();
446
447    // check if we clipped out
448    if (true || NULL == glyph->fPlot) {
449        int x = vx >> 16;
450        int y = vy >> 16;
451        if (fClipRect.quickReject(x, y, x + width, y + height)) {
452//            SkCLZ(3);    // so we can set a break-point in the debugger
453            return;
454        }
455    }
456
457    if (NULL == glyph->fPlot) {
458        if (!fStrike->glyphTooLargeForAtlas(glyph)) {
459            if (fStrike->addGlyphToAtlas(glyph, scaler)) {
460                goto HAS_ATLAS;
461            }
462
463            // try to clear out an unused plot before we flush
464            if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
465                fStrike->addGlyphToAtlas(glyph, scaler)) {
466                goto HAS_ATLAS;
467            }
468
469            if (c_DumpFontCache) {
470#ifdef SK_DEVELOPER
471                fContext->getFontCache()->dump();
472#endif
473            }
474
475            // flush any accumulated draws to allow us to free up a plot
476            this->flushGlyphs();
477            fContext->flush();
478
479            // we should have an unused plot now
480            if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
481                fStrike->addGlyphToAtlas(glyph, scaler)) {
482                goto HAS_ATLAS;
483            }
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), kTextVAColorSize);
534        } else {
535            fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
536                SK_ARRAY_COUNT(gTextVertexAttribs), kTextVASize);
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), kTextVAColorSize);
545            } else {
546                fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
547                    SK_ARRAY_COUNT(gTextVertexAttribs), kTextVASize);
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().getVertexStride());
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        if (0xFF == GrColorUnpackA(fPaint.getColor())) {
598            fDrawTarget->drawState()->setHint(GrDrawState::kVertexColorsAreOpaque_Hint, true);
599        }
600        // color comes after position.
601        GrColor* colors = reinterpret_cast<GrColor*>(positions + 1);
602        for (int i = 0; i < 4; ++i) {
603           *colors = fPaint.getColor();
604           colors = reinterpret_cast<GrColor*>(reinterpret_cast<intptr_t>(colors) + vertSize);
605        }
606    }
607    fCurrVertex += 4;
608}
609