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 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 67 GrBitmapTextContext::~GrBitmapTextContext() { 68 this->flushGlyphs(); 69 } 70 71 bool GrBitmapTextContext::canDraw(const SkPaint& paint) { 72 return !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix()); 73 } 74 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 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 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 176 inline void GrBitmapTextContext::finish() { 177 this->flushGlyphs(); 178 179 GrTextContext::finish(); 180 } 181 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 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 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