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 namespace { 34 // position + texture coord 35 extern const GrVertexAttrib gTextVertexAttribs[] = { 36 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, 37 {kVec2f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding} 38 }; 39 40 static const size_t kTextVASize = 2 * sizeof(SkPoint); 41 42 // position + color + texture coord 43 extern 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 49 static const size_t kTextVAColorSize = 2 * sizeof(SkPoint) + sizeof(GrColor); 50 51 }; 52 53 GrBitmapTextContext::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 68 GrBitmapTextContext::~GrBitmapTextContext() { 69 this->flushGlyphs(); 70 } 71 72 bool GrBitmapTextContext::canDraw(const SkPaint& paint) { 73 return !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix()); 74 } 75 76 static 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 83 void 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 162 inline 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 174 inline void GrBitmapTextContext::finish() { 175 this->flushGlyphs(); 176 177 GrTextContext::finish(); 178 } 179 180 void 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 271 void 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, ¤tText, 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 424 void 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 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), 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