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 "GrDistanceFieldTextContext.h" 9 #include "GrAtlas.h" 10 #include "SkColorFilter.h" 11 #include "GrDrawTarget.h" 12 #include "GrDrawTargetCaps.h" 13 #include "GrFontScaler.h" 14 #include "SkGlyphCache.h" 15 #include "GrGpu.h" 16 #include "GrIndexBuffer.h" 17 #include "GrStrokeInfo.h" 18 #include "GrTextStrike.h" 19 #include "GrTextStrike_impl.h" 20 #include "SkDistanceFieldGen.h" 21 #include "SkDraw.h" 22 #include "SkGpuDevice.h" 23 #include "SkPath.h" 24 #include "SkRTConf.h" 25 #include "SkStrokeRec.h" 26 #include "effects/GrDistanceFieldTextureEffect.h" 27 28 static const int kGlyphCoordsAttributeIndex = 1; 29 30 static const int kSmallDFFontSize = 32; 31 static const int kSmallDFFontLimit = 32; 32 static const int kMediumDFFontSize = 64; 33 static const int kMediumDFFontLimit = 64; 34 static const int kLargeDFFontSize = 128; 35 36 SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false, 37 "Dump the contents of the font cache before every purge."); 38 39 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context, 40 const SkDeviceProperties& properties, 41 bool enable) 42 : GrTextContext(context, properties) { 43 #if SK_FORCE_DISTANCEFIELD_FONTS 44 fEnableDFRendering = true; 45 #else 46 fEnableDFRendering = enable; 47 #endif 48 fStrike = NULL; 49 fGammaTexture = NULL; 50 51 fCurrTexture = NULL; 52 fCurrVertex = 0; 53 54 fVertices = NULL; 55 fMaxVertices = 0; 56 } 57 58 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() { 59 this->flushGlyphs(); 60 SkSafeSetNull(fGammaTexture); 61 } 62 63 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) { 64 if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP()) { 65 return false; 66 } 67 68 // rasterizers and mask filters modify alpha, which doesn't 69 // translate well to distance 70 if (paint.getRasterizer() || paint.getMaskFilter() || 71 !fContext->getTextTarget()->caps()->shaderDerivativeSupport()) { 72 return false; 73 } 74 75 // TODO: add some stroking support 76 if (paint.getStyle() != SkPaint::kFill_Style) { 77 return false; 78 } 79 80 // TODO: choose an appropriate maximum scale for distance fields and 81 // enable perspective 82 if (SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix())) { 83 return false; 84 } 85 86 // distance fields cannot represent color fonts 87 SkScalerContext::Rec rec; 88 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec); 89 return rec.getFormat() != SkMask::kARGB32_Format; 90 } 91 92 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) { 93 unsigned r = SkColorGetR(c); 94 unsigned g = SkColorGetG(c); 95 unsigned b = SkColorGetB(c); 96 return GrColorPackRGBA(r, g, b, 0xff); 97 } 98 99 void GrDistanceFieldTextContext::flushGlyphs() { 100 if (NULL == fDrawTarget) { 101 return; 102 } 103 104 GrDrawState* drawState = fDrawTarget->drawState(); 105 GrDrawState::AutoRestoreEffects are(drawState); 106 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget()); 107 108 if (fCurrVertex > 0) { 109 // setup our sampler state for our text texture/atlas 110 SkASSERT(SkIsAlign4(fCurrVertex)); 111 SkASSERT(fCurrTexture); 112 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode); 113 GrTextureParams gammaParams(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode); 114 115 // Effects could be stored with one of the cache objects (atlas?) 116 SkColor filteredColor; 117 SkColorFilter* colorFilter = fSkPaint.getColorFilter(); 118 if (NULL != colorFilter) { 119 filteredColor = colorFilter->filterColor(fSkPaint.getColor()); 120 } else { 121 filteredColor = fSkPaint.getColor(); 122 } 123 if (fUseLCDText) { 124 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor); 125 bool useBGR = SkDeviceProperties::Geometry::kBGR_Layout == 126 fDeviceProperties.fGeometry.getLayout(); 127 drawState->addCoverageEffect(GrDistanceFieldLCDTextureEffect::Create( 128 fCurrTexture, 129 params, 130 fGammaTexture, 131 gammaParams, 132 colorNoPreMul, 133 fContext->getMatrix().rectStaysRect() && 134 fContext->getMatrix().isSimilarity(), 135 useBGR), 136 kGlyphCoordsAttributeIndex)->unref(); 137 138 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() || 139 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() || 140 fPaint.numColorStages()) { 141 GrPrintf("LCD Text will not draw correctly.\n"); 142 } 143 // We don't use the GrPaint's color in this case because it's been premultiplied by 144 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by 145 // the mask texture color. The end result is that we get 146 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor 147 int a = SkColorGetA(fSkPaint.getColor()); 148 // paintAlpha 149 drawState->setColor(SkColorSetARGB(a, a, a, a)); 150 // paintColor 151 drawState->setBlendConstant(colorNoPreMul); 152 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff); 153 } else { 154 #ifdef SK_GAMMA_APPLY_TO_A8 155 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDeviceProperties.fGamma, 156 filteredColor); 157 drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create( 158 fCurrTexture, params, 159 fGammaTexture, gammaParams, 160 lum/255.f, 161 fContext->getMatrix().isSimilarity()), 162 kGlyphCoordsAttributeIndex)->unref(); 163 #else 164 drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create( 165 fCurrTexture, params, 166 fContext->getMatrix().isSimilarity()), 167 kGlyphCoordsAttributeIndex)->unref(); 168 #endif 169 // set back to normal in case we took LCD path previously. 170 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff()); 171 drawState->setColor(fPaint.getColor()); 172 } 173 174 int nGlyphs = fCurrVertex / 4; 175 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer()); 176 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType, 177 nGlyphs, 178 4, 6); 179 fDrawTarget->resetVertexSource(); 180 fVertices = NULL; 181 fMaxVertices = 0; 182 fCurrVertex = 0; 183 SkSafeSetNull(fCurrTexture); 184 } 185 } 186 187 namespace { 188 189 // position + texture coord 190 extern const GrVertexAttrib gTextVertexAttribs[] = { 191 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, 192 {kVec2f_GrVertexAttribType, sizeof(SkPoint), kEffect_GrVertexAttribBinding} 193 }; 194 195 }; 196 197 void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed, 198 SkFixed vx, SkFixed vy, 199 GrFontScaler* scaler) { 200 if (NULL == fDrawTarget) { 201 return; 202 } 203 if (NULL == fStrike) { 204 fStrike = fContext->getFontCache()->getStrike(scaler, true); 205 } 206 207 GrGlyph* glyph = fStrike->getGlyph(packed, scaler); 208 if (NULL == glyph || glyph->fBounds.isEmpty()) { 209 return; 210 } 211 212 SkScalar sx = SkFixedToScalar(vx); 213 SkScalar sy = SkFixedToScalar(vy); 214 /* 215 // not valid, need to find a different solution for this 216 vx += SkIntToFixed(glyph->fBounds.fLeft); 217 vy += SkIntToFixed(glyph->fBounds.fTop); 218 219 // keep them as ints until we've done the clip-test 220 GrFixed width = glyph->fBounds.width(); 221 GrFixed height = glyph->fBounds.height(); 222 223 // check if we clipped out 224 if (true || NULL == glyph->fPlot) { 225 int x = vx >> 16; 226 int y = vy >> 16; 227 if (fClipRect.quickReject(x, y, x + width, y + height)) { 228 // SkCLZ(3); // so we can set a break-point in the debugger 229 return; 230 } 231 } 232 */ 233 if (NULL == glyph->fPlot) { 234 if (fStrike->addGlyphToAtlas(glyph, scaler)) { 235 goto HAS_ATLAS; 236 } 237 238 // try to clear out an unused plot before we flush 239 if (fContext->getFontCache()->freeUnusedPlot(fStrike) && 240 fStrike->addGlyphToAtlas(glyph, scaler)) { 241 goto HAS_ATLAS; 242 } 243 244 if (c_DumpFontCache) { 245 #ifdef SK_DEVELOPER 246 fContext->getFontCache()->dump(); 247 #endif 248 } 249 250 // before we purge the cache, we must flush any accumulated draws 251 this->flushGlyphs(); 252 fContext->flush(); 253 254 // we should have an unused plot now 255 if (fContext->getFontCache()->freeUnusedPlot(fStrike) && 256 fStrike->addGlyphToAtlas(glyph, scaler)) { 257 goto HAS_ATLAS; 258 } 259 260 if (NULL == glyph->fPath) { 261 SkPath* path = SkNEW(SkPath); 262 if (!scaler->getGlyphPath(glyph->glyphID(), path)) { 263 // flag the glyph as being dead? 264 delete path; 265 return; 266 } 267 glyph->fPath = path; 268 } 269 270 GrContext::AutoMatrix am; 271 SkMatrix ctm; 272 ctm.setScale(fTextRatio, fTextRatio); 273 ctm.postTranslate(sx, sy); 274 GrPaint tmpPaint(fPaint); 275 am.setPreConcat(fContext, ctm, &tmpPaint); 276 GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle); 277 fContext->drawPath(tmpPaint, *glyph->fPath, strokeInfo); 278 return; 279 } 280 281 HAS_ATLAS: 282 SkASSERT(glyph->fPlot); 283 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken(); 284 glyph->fPlot->setDrawToken(drawToken); 285 286 GrTexture* texture = glyph->fPlot->texture(); 287 SkASSERT(texture); 288 289 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) { 290 this->flushGlyphs(); 291 fCurrTexture = texture; 292 fCurrTexture->ref(); 293 } 294 295 if (NULL == fVertices) { 296 // If we need to reserve vertices allow the draw target to suggest 297 // a number of verts to reserve and whether to perform a flush. 298 fMaxVertices = kMinRequestedVerts; 299 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>( 300 SK_ARRAY_COUNT(gTextVertexAttribs)); 301 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL); 302 if (flush) { 303 this->flushGlyphs(); 304 fContext->flush(); 305 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>( 306 SK_ARRAY_COUNT(gTextVertexAttribs)); 307 } 308 fMaxVertices = kDefaultRequestedVerts; 309 // ignore return, no point in flushing again. 310 fDrawTarget->geometryHints(&fMaxVertices, NULL); 311 312 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads(); 313 if (fMaxVertices < kMinRequestedVerts) { 314 fMaxVertices = kDefaultRequestedVerts; 315 } else if (fMaxVertices > maxQuadVertices) { 316 // don't exceed the limit of the index buffer 317 fMaxVertices = maxQuadVertices; 318 } 319 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices, 320 0, 321 GrTCast<void**>(&fVertices), 322 NULL); 323 GrAlwaysAssert(success); 324 SkASSERT(2*sizeof(SkPoint) == fDrawTarget->getDrawState().getVertexSize()); 325 } 326 327 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset); 328 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset); 329 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2*SK_DistanceFieldInset); 330 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2*SK_DistanceFieldInset); 331 332 SkScalar scale = fTextRatio; 333 dx *= scale; 334 dy *= scale; 335 sx += dx; 336 sy += dy; 337 width *= scale; 338 height *= scale; 339 340 SkFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX + SK_DistanceFieldInset); 341 SkFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY + SK_DistanceFieldInset); 342 SkFixed tw = SkIntToFixed(glyph->fBounds.width() - 2*SK_DistanceFieldInset); 343 SkFixed th = SkIntToFixed(glyph->fBounds.height() - 2*SK_DistanceFieldInset); 344 345 static const size_t kVertexSize = 2 * sizeof(SkPoint); 346 fVertices[2*fCurrVertex].setRectFan(sx, 347 sy, 348 sx + width, 349 sy + height, 350 kVertexSize); 351 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)), 352 SkFixedToFloat(texture->normalizeFixedY(ty)), 353 SkFixedToFloat(texture->normalizeFixedX(tx + tw)), 354 SkFixedToFloat(texture->normalizeFixedY(ty + th)), 355 kVertexSize); 356 fCurrVertex += 4; 357 } 358 359 inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) { 360 GrTextContext::init(paint, skPaint); 361 362 fStrike = NULL; 363 364 fCurrTexture = NULL; 365 fCurrVertex = 0; 366 367 fVertices = NULL; 368 fMaxVertices = 0; 369 370 if (fSkPaint.getTextSize() <= kSmallDFFontLimit) { 371 fTextRatio = fSkPaint.getTextSize()/kSmallDFFontSize; 372 fSkPaint.setTextSize(SkIntToScalar(kSmallDFFontSize)); 373 } else if (fSkPaint.getTextSize() <= kMediumDFFontLimit) { 374 fTextRatio = fSkPaint.getTextSize()/kMediumDFFontSize; 375 fSkPaint.setTextSize(SkIntToScalar(kMediumDFFontSize)); 376 } else { 377 fTextRatio = fSkPaint.getTextSize()/kLargeDFFontSize; 378 fSkPaint.setTextSize(SkIntToScalar(kLargeDFFontSize)); 379 } 380 381 fUseLCDText = fSkPaint.isLCDRenderText(); 382 383 fSkPaint.setLCDRenderText(false); 384 fSkPaint.setAutohinted(false); 385 fSkPaint.setHinting(SkPaint::kNormal_Hinting); 386 fSkPaint.setSubpixelText(true); 387 388 } 389 390 inline void GrDistanceFieldTextContext::finish() { 391 flushGlyphs(); 392 393 GrTextContext::finish(); 394 } 395 396 static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache, 397 const SkDeviceProperties& deviceProperties, 398 GrTexture** gammaTexture) { 399 if (NULL == *gammaTexture) { 400 int width, height; 401 size_t size; 402 403 #ifdef SK_GAMMA_CONTRAST 404 SkScalar contrast = SK_GAMMA_CONTRAST; 405 #else 406 SkScalar contrast = 0.5f; 407 #endif 408 SkScalar paintGamma = deviceProperties.fGamma; 409 SkScalar deviceGamma = deviceProperties.fGamma; 410 411 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma, 412 &width, &height); 413 414 SkAutoTArray<uint8_t> data((int)size); 415 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get()); 416 417 // TODO: Update this to use the cache rather than directly creating a texture. 418 GrTextureDesc desc; 419 desc.fFlags = kDynamicUpdate_GrTextureFlagBit; 420 desc.fWidth = width; 421 desc.fHeight = height; 422 desc.fConfig = kAlpha_8_GrPixelConfig; 423 424 *gammaTexture = context->getGpu()->createTexture(desc, NULL, 0); 425 if (NULL == *gammaTexture) { 426 return; 427 } 428 429 context->writeTexturePixels(*gammaTexture, 430 0, 0, width, height, 431 (*gammaTexture)->config(), data.get(), 0, 432 GrContext::kDontFlush_PixelOpsFlag); 433 } 434 } 435 436 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint, 437 const char text[], size_t byteLength, 438 SkScalar x, SkScalar y) { 439 SkASSERT(byteLength == 0 || text != NULL); 440 441 // nothing to draw or can't draw 442 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/ 443 || fSkPaint.getRasterizer()) { 444 return; 445 } 446 447 this->init(paint, skPaint); 448 449 SkScalar sizeRatio = fTextRatio; 450 451 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); 452 453 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL); 454 SkGlyphCache* cache = autoCache.getCache(); 455 GrFontScaler* fontScaler = GetGrFontScaler(cache); 456 457 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture); 458 459 // need to measure first 460 // TODO - generate positions and pre-load cache as well? 461 const char* stop = text + byteLength; 462 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { 463 SkFixed stopX = 0; 464 SkFixed stopY = 0; 465 466 const char* textPtr = text; 467 while (textPtr < stop) { 468 // don't need x, y here, since all subpixel variants will have the 469 // same advance 470 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0); 471 472 stopX += glyph.fAdvanceX; 473 stopY += glyph.fAdvanceY; 474 } 475 SkASSERT(textPtr == stop); 476 477 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio; 478 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio; 479 480 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { 481 alignX = SkScalarHalf(alignX); 482 alignY = SkScalarHalf(alignY); 483 } 484 485 x -= alignX; 486 y -= alignY; 487 } 488 489 SkFixed fx = SkScalarToFixed(x); 490 SkFixed fy = SkScalarToFixed(y); 491 SkFixed fixedScale = SkScalarToFixed(sizeRatio); 492 while (text < stop) { 493 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); 494 495 if (glyph.fWidth) { 496 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(), 497 glyph.getSubXFixed(), 498 glyph.getSubYFixed()), 499 fx, 500 fy, 501 fontScaler); 502 } 503 504 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale); 505 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale); 506 } 507 508 this->finish(); 509 } 510 511 void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint, 512 const char text[], size_t byteLength, 513 const SkScalar pos[], SkScalar constY, 514 int scalarsPerPosition) { 515 516 SkASSERT(byteLength == 0 || text != NULL); 517 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); 518 519 // nothing to draw 520 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) { 521 return; 522 } 523 524 this->init(paint, skPaint); 525 526 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); 527 528 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL); 529 SkGlyphCache* cache = autoCache.getCache(); 530 GrFontScaler* fontScaler = GetGrFontScaler(cache); 531 532 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture); 533 534 const char* stop = text + byteLength; 535 536 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { 537 while (text < stop) { 538 // the last 2 parameters are ignored 539 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); 540 541 if (glyph.fWidth) { 542 SkScalar x = pos[0]; 543 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1]; 544 545 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(), 546 glyph.getSubXFixed(), 547 glyph.getSubYFixed()), 548 SkScalarToFixed(x), 549 SkScalarToFixed(y), 550 fontScaler); 551 } 552 pos += scalarsPerPosition; 553 } 554 } else { 555 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0; 556 while (text < stop) { 557 // the last 2 parameters are ignored 558 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); 559 560 if (glyph.fWidth) { 561 SkScalar x = pos[0]; 562 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1]; 563 564 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(), 565 glyph.getSubXFixed(), 566 glyph.getSubYFixed()), 567 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift), 568 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift), 569 fontScaler); 570 } 571 pos += scalarsPerPosition; 572 } 573 } 574 575 this->finish(); 576 } 577