GrStencilAndCoverTextContext.cpp revision 640789d64bcce565856fde5bbfc845d56bb470c8
1/* 2 * Copyright 2014 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 "GrStencilAndCoverTextContext.h" 9#include "GrAtlasTextContext.h" 10#include "GrContext.h" 11#include "GrDrawContext.h" 12#include "GrPath.h" 13#include "GrPathRange.h" 14#include "GrResourceProvider.h" 15#include "GrTextUtils.h" 16#include "SkAutoKern.h" 17#include "SkDraw.h" 18#include "SkDrawProcs.h" 19#include "SkGlyphCache.h" 20#include "SkGrPriv.h" 21#include "SkDrawFilter.h" 22#include "SkPath.h" 23#include "SkTextBlobRunIterator.h" 24#include "SkTextMapStateProc.h" 25#include "SkTextFormatParams.h" 26 27#include "batches/GrDrawPathBatch.h" 28 29template<typename Key, typename Val> static void delete_hash_map_entry(const Key&, Val* val) { 30 SkASSERT(*val); 31 delete *val; 32} 33 34template<typename T> static void delete_hash_table_entry(T* val) { 35 SkASSERT(*val); 36 delete *val; 37} 38 39GrStencilAndCoverTextContext::GrStencilAndCoverTextContext() 40 : fFallbackTextContext(nullptr) 41 , fCacheSize(0) { 42} 43 44GrStencilAndCoverTextContext* 45GrStencilAndCoverTextContext::Create() { 46 GrStencilAndCoverTextContext* textContext = new GrStencilAndCoverTextContext(); 47 textContext->fFallbackTextContext = GrAtlasTextContext::Create(); 48 49 return textContext; 50} 51 52GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() { 53 delete fFallbackTextContext; 54 fBlobIdCache.foreach(delete_hash_map_entry<uint32_t, TextBlob*>); 55 fBlobKeyCache.foreach(delete_hash_table_entry<TextBlob*>); 56} 57 58bool GrStencilAndCoverTextContext::internalCanDraw(const SkPaint& skPaint) { 59 if (skPaint.getRasterizer()) { 60 return false; 61 } 62 if (skPaint.getMaskFilter()) { 63 return false; 64 } 65 if (SkPathEffect* pe = skPaint.getPathEffect()) { 66 if (pe->asADash(nullptr) != SkPathEffect::kDash_DashType) { 67 return false; 68 } 69 } 70 // No hairlines. They would require new paths with customized strokes for every new draw matrix. 71 return SkPaint::kStroke_Style != skPaint.getStyle() || 0 != skPaint.getStrokeWidth(); 72} 73 74void GrStencilAndCoverTextContext::drawText(GrContext* context, GrDrawContext* dc, 75 const GrClip& clip, const GrPaint& paint, 76 const SkPaint& skPaint, const SkMatrix& viewMatrix, 77 const SkSurfaceProps& props, 78 const char text[], size_t byteLength, 79 SkScalar x, SkScalar y, const SkIRect& clipBounds) { 80 if (context->abandoned()) { 81 return; 82 } else if (this->canDraw(skPaint, viewMatrix)) { 83 if (skPaint.getTextSize() > 0) { 84 TextRun run(skPaint); 85 run.setText(text, byteLength, x, y); 86 run.draw(context, dc, paint, clip, paint.getColor(), viewMatrix, props, 0, 0, 87 clipBounds, fFallbackTextContext, skPaint); 88 } 89 return; 90 } else if (fFallbackTextContext->canDraw(skPaint, viewMatrix, props, 91 *context->caps()->shaderCaps())) { 92 fFallbackTextContext->drawText(context, dc, clip, paint, skPaint, viewMatrix, props, text, 93 byteLength, x, y, clipBounds); 94 return; 95 } 96 97 // fall back to drawing as a path 98 GrTextUtils::DrawTextAsPath(context, dc, clip, skPaint, viewMatrix, text, byteLength, x, y, 99 clipBounds); 100} 101 102void GrStencilAndCoverTextContext::drawPosText(GrContext* context, GrDrawContext* dc, 103 const GrClip& clip, 104 const GrPaint& paint, 105 const SkPaint& skPaint, 106 const SkMatrix& viewMatrix, 107 const SkSurfaceProps& props, 108 const char text[], 109 size_t byteLength, 110 const SkScalar pos[], 111 int scalarsPerPosition, 112 const SkPoint& offset, 113 const SkIRect& clipBounds) { 114 if (context->abandoned()) { 115 return; 116 } else if (this->canDraw(skPaint, viewMatrix)) { 117 if (skPaint.getTextSize() > 0) { 118 TextRun run(skPaint); 119 run.setPosText(text, byteLength, pos, scalarsPerPosition, offset); 120 run.draw(context, dc, paint, clip, paint.getColor(), viewMatrix, props, 0, 0, 121 clipBounds, fFallbackTextContext, skPaint); 122 } 123 return; 124 } else if (fFallbackTextContext->canDraw(skPaint, viewMatrix, props, 125 *context->caps()->shaderCaps())) { 126 fFallbackTextContext->drawPosText(context, dc, clip, paint, skPaint, viewMatrix, props, 127 text, byteLength, pos, 128 scalarsPerPosition, offset, clipBounds); 129 return; 130 } 131 132 // fall back to drawing as a path 133 GrTextUtils::DrawPosTextAsPath(context, dc, props, clip, skPaint, viewMatrix, text, 134 byteLength, pos, scalarsPerPosition, offset, clipBounds); 135} 136 137void GrStencilAndCoverTextContext::uncachedDrawTextBlob(GrContext* context, 138 GrDrawContext* dc, 139 const GrClip& clip, const SkPaint& skPaint, 140 const SkMatrix& viewMatrix, 141 const SkSurfaceProps& props, 142 const SkTextBlob* blob, 143 SkScalar x, SkScalar y, 144 SkDrawFilter* drawFilter, 145 const SkIRect& clipBounds) { 146 SkPaint runPaint = skPaint; 147 148 SkTextBlobRunIterator it(blob); 149 for (;!it.done(); it.next()) { 150 size_t textLen = it.glyphCount() * sizeof(uint16_t); 151 const SkPoint& offset = it.offset(); 152 153 // applyFontToPaint() always overwrites the exact same attributes, 154 // so it is safe to not re-seed the paint for this reason. 155 it.applyFontToPaint(&runPaint); 156 157 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) { 158 // A false return from filter() means we should abort the current draw. 159 runPaint = skPaint; 160 continue; 161 } 162 163 runPaint.setFlags(GrTextUtils::FilterTextFlags(props, runPaint)); 164 165 GrPaint grPaint; 166 if (!SkPaintToGrPaint(context, runPaint, viewMatrix, dc->isGammaCorrect(), &grPaint)) { 167 return; 168 } 169 170 switch (it.positioning()) { 171 case SkTextBlob::kDefault_Positioning: 172 this->drawText(context, dc, clip, grPaint, runPaint, viewMatrix, props, 173 (const char *)it.glyphs(), 174 textLen, x + offset.x(), y + offset.y(), clipBounds); 175 break; 176 case SkTextBlob::kHorizontal_Positioning: 177 this->drawPosText(context, dc, clip, grPaint, runPaint, viewMatrix, props, 178 (const char*)it.glyphs(), 179 textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()), 180 clipBounds); 181 break; 182 case SkTextBlob::kFull_Positioning: 183 this->drawPosText(context, dc, clip, grPaint, runPaint, viewMatrix, props, 184 (const char*)it.glyphs(), 185 textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds); 186 break; 187 } 188 189 if (drawFilter) { 190 // A draw filter may change the paint arbitrarily, so we must re-seed in this case. 191 runPaint = skPaint; 192 } 193 } 194} 195 196void GrStencilAndCoverTextContext::drawTextBlob(GrContext* context, GrDrawContext* dc, 197 const GrClip& clip, const SkPaint& skPaint, 198 const SkMatrix& viewMatrix, 199 const SkSurfaceProps& props, 200 const SkTextBlob* skBlob, SkScalar x, SkScalar y, 201 SkDrawFilter* drawFilter, 202 const SkIRect& clipBounds) { 203 if (context->abandoned()) { 204 return; 205 } 206 207 if (!this->internalCanDraw(skPaint)) { 208 fFallbackTextContext->drawTextBlob(context, dc, clip, skPaint, viewMatrix, props, skBlob, 209 x, y, drawFilter, clipBounds); 210 return; 211 } 212 213 if (drawFilter || skPaint.getPathEffect()) { 214 // This draw can't be cached. 215 this->uncachedDrawTextBlob(context, dc, clip, skPaint, viewMatrix, props, skBlob, x, y, 216 drawFilter, clipBounds); 217 return; 218 } 219 220 GrPaint paint; 221 if (!SkPaintToGrPaint(context, skPaint, viewMatrix, dc->isGammaCorrect(), &paint)) { 222 return; 223 } 224 225 const TextBlob& blob = this->findOrCreateTextBlob(skBlob, skPaint); 226 227 TextBlob::Iter iter(blob); 228 for (TextRun* run = iter.get(); run; run = iter.next()) { 229 run->draw(context, dc, paint, clip, paint.getColor(), viewMatrix, props, x, y, 230 clipBounds, fFallbackTextContext, skPaint); 231 run->releaseGlyphCache(); 232 } 233} 234 235static inline int style_key_cnt(const GrStyle& style) { 236 int cnt = GrStyle::KeySize(style, GrStyle::Apply::kPathEffectAndStrokeRec); 237 // We should be able to make a key because we filtered out arbitrary path effects. 238 SkASSERT(cnt > 0); 239 return cnt; 240} 241 242static inline void write_style_key(uint32_t* dst, const GrStyle& style) { 243 // Pass 1 for the scale since the GPU will apply the style not GrStyle::applyToPath(). 244 GrStyle::WriteKey(dst, style, GrStyle::Apply::kPathEffectAndStrokeRec, SK_Scalar1); 245} 246 247const GrStencilAndCoverTextContext::TextBlob& 248GrStencilAndCoverTextContext::findOrCreateTextBlob(const SkTextBlob* skBlob, 249 const SkPaint& skPaint) { 250 // The font-related parameters are baked into the text blob and will override this skPaint, so 251 // the only remaining properties that can affect a TextBlob are the ones related to stroke. 252 if (SkPaint::kFill_Style == skPaint.getStyle()) { // Fast path. 253 if (TextBlob** found = fBlobIdCache.find(skBlob->uniqueID())) { 254 fLRUList.remove(*found); 255 fLRUList.addToTail(*found); 256 return **found; 257 } 258 TextBlob* blob = new TextBlob(skBlob->uniqueID(), skBlob, skPaint); 259 this->purgeToFit(*blob); 260 fBlobIdCache.set(skBlob->uniqueID(), blob); 261 fLRUList.addToTail(blob); 262 fCacheSize += blob->cpuMemorySize(); 263 return *blob; 264 } else { 265 GrStyle style(skPaint); 266 SkSTArray<4, uint32_t, true> key; 267 key.reset(1 + style_key_cnt(style)); 268 key[0] = skBlob->uniqueID(); 269 write_style_key(&key[1], style); 270 if (TextBlob** found = fBlobKeyCache.find(key)) { 271 fLRUList.remove(*found); 272 fLRUList.addToTail(*found); 273 return **found; 274 } 275 TextBlob* blob = new TextBlob(key, skBlob, skPaint); 276 this->purgeToFit(*blob); 277 fBlobKeyCache.set(blob); 278 fLRUList.addToTail(blob); 279 fCacheSize += blob->cpuMemorySize(); 280 return *blob; 281 } 282} 283 284void GrStencilAndCoverTextContext::purgeToFit(const TextBlob& blob) { 285 static const size_t maxCacheSize = 4 * 1024 * 1024; // Allow up to 4 MB for caching text blobs. 286 287 size_t maxSizeForNewBlob = maxCacheSize - blob.cpuMemorySize(); 288 while (fCacheSize && fCacheSize > maxSizeForNewBlob) { 289 TextBlob* lru = fLRUList.head(); 290 if (1 == lru->key().count()) { 291 // 1-length keys are unterstood to be the blob id. 292 fBlobIdCache.remove(lru->key()[0]); 293 } else { 294 fBlobKeyCache.remove(lru->key()); 295 } 296 fLRUList.remove(lru); 297 fCacheSize -= lru->cpuMemorySize(); 298 delete lru; 299 } 300} 301 302//////////////////////////////////////////////////////////////////////////////////////////////////// 303 304void GrStencilAndCoverTextContext::TextBlob::init(const SkTextBlob* skBlob, 305 const SkPaint& skPaint) { 306 fCpuMemorySize = sizeof(TextBlob); 307 SkPaint runPaint(skPaint); 308 for (SkTextBlobRunIterator iter(skBlob); !iter.done(); iter.next()) { 309 iter.applyFontToPaint(&runPaint); // No need to re-seed the paint. 310 if (runPaint.getTextSize() <= 0) { 311 continue; 312 } 313 TextRun* run = this->addToTail(runPaint); 314 315 const char* text = reinterpret_cast<const char*>(iter.glyphs()); 316 size_t byteLength = sizeof(uint16_t) * iter.glyphCount(); 317 const SkPoint& runOffset = iter.offset(); 318 319 switch (iter.positioning()) { 320 case SkTextBlob::kDefault_Positioning: 321 run->setText(text, byteLength, runOffset.fX, runOffset.fY); 322 break; 323 case SkTextBlob::kHorizontal_Positioning: 324 run->setPosText(text, byteLength, iter.pos(), 1, SkPoint::Make(0, runOffset.fY)); 325 break; 326 case SkTextBlob::kFull_Positioning: 327 run->setPosText(text, byteLength, iter.pos(), 2, SkPoint::Make(0, 0)); 328 break; 329 } 330 331 fCpuMemorySize += run->computeSizeInCache(); 332 } 333} 334 335//////////////////////////////////////////////////////////////////////////////////////////////////// 336 337class GrStencilAndCoverTextContext::FallbackBlobBuilder { 338public: 339 FallbackBlobBuilder() : fBuffIdx(0), fCount(0) {} 340 341 bool isInitialized() const { return fBuilder != nullptr; } 342 343 void init(const SkPaint& font, SkScalar textRatio); 344 345 void appendGlyph(uint16_t glyphId, const SkPoint& pos); 346 347 const SkTextBlob* buildIfNeeded(int* count); 348 349private: 350 enum { kWriteBufferSize = 1024 }; 351 352 void flush(); 353 354 SkAutoTDelete<SkTextBlobBuilder> fBuilder; 355 SkPaint fFont; 356 int fBuffIdx; 357 int fCount; 358 uint16_t fGlyphIds[kWriteBufferSize]; 359 SkPoint fPositions[kWriteBufferSize]; 360}; 361 362//////////////////////////////////////////////////////////////////////////////////////////////////// 363 364GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke) 365 : fStyle(fontAndStroke), 366 fFont(fontAndStroke), 367 fTotalGlyphCount(0), 368 fFallbackGlyphCount(0), 369 fDetachedGlyphCache(nullptr), 370 fLastDrawnGlyphsID(SK_InvalidUniqueID) { 371 SkASSERT(fFont.getTextSize() > 0); 372 SkASSERT(!fStyle.hasNonDashPathEffect()); // Arbitrary path effects not supported. 373 SkASSERT(!fStyle.isSimpleHairline()); // Hairlines are not supported. 374 375 // Setting to "fill" ensures that no strokes get baked into font outlines. (We use the GPU path 376 // rendering API for stroking). 377 fFont.setStyle(SkPaint::kFill_Style); 378 379 if (fFont.isFakeBoldText() && fStyle.isSimpleFill()) { 380 const SkStrokeRec& stroke = fStyle.strokeRec(); 381 // Instead of letting fake bold get baked into the glyph outlines, do it with GPU stroke. 382 SkScalar fakeBoldScale = SkScalarInterpFunc(fFont.getTextSize(), 383 kStdFakeBoldInterpKeys, 384 kStdFakeBoldInterpValues, 385 kStdFakeBoldInterpLength); 386 SkScalar extra = SkScalarMul(fFont.getTextSize(), fakeBoldScale); 387 388 SkStrokeRec strokeRec(SkStrokeRec::kFill_InitStyle); 389 strokeRec.setStrokeStyle(stroke.needToApply() ? stroke.getWidth() + extra : extra, 390 true /*strokeAndFill*/); 391 fStyle = GrStyle(strokeRec, fStyle.pathEffect()); 392 fFont.setFakeBoldText(false); 393 } 394 395 if (!fFont.getPathEffect() && !fStyle.isDashed()) { 396 const SkStrokeRec& stroke = fStyle.strokeRec(); 397 // We can draw the glyphs from canonically sized paths. 398 fTextRatio = fFont.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; 399 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fFont.getTextSize(); 400 401 // Compensate for the glyphs being scaled by fTextRatio. 402 if (!fStyle.isSimpleFill()) { 403 SkStrokeRec strokeRec(SkStrokeRec::kFill_InitStyle); 404 strokeRec.setStrokeStyle(stroke.getWidth() / fTextRatio, 405 SkStrokeRec::kStrokeAndFill_Style == stroke.getStyle()); 406 fStyle = GrStyle(strokeRec, fStyle.pathEffect()); 407 } 408 409 fFont.setLinearText(true); 410 fFont.setLCDRenderText(false); 411 fFont.setAutohinted(false); 412 fFont.setHinting(SkPaint::kNo_Hinting); 413 fFont.setSubpixelText(true); 414 fFont.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); 415 416 fUsingRawGlyphPaths = SK_Scalar1 == fFont.getTextScaleX() && 417 0 == fFont.getTextSkewX() && 418 !fFont.isFakeBoldText() && 419 !fFont.isVerticalText(); 420 } else { 421 fTextRatio = fTextInverseRatio = 1.0f; 422 fUsingRawGlyphPaths = false; 423 } 424 425 // Generate the key that will be used to cache the GPU glyph path objects. 426 if (fUsingRawGlyphPaths && fStyle.isSimpleFill()) { 427 static const GrUniqueKey::Domain kRawFillPathGlyphDomain = GrUniqueKey::GenerateDomain(); 428 429 const SkTypeface* typeface = fFont.getTypeface(); 430 GrUniqueKey::Builder builder(&fGlyphPathsKey, kRawFillPathGlyphDomain, 1); 431 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0; 432 } else { 433 static const GrUniqueKey::Domain kPathGlyphDomain = GrUniqueKey::GenerateDomain(); 434 435 int styleDataCount = GrStyle::KeySize(fStyle, GrStyle::Apply::kPathEffectAndStrokeRec); 436 // Key should be valid since we opted out of drawing arbitrary path effects. 437 SkASSERT(styleDataCount >= 0); 438 if (fUsingRawGlyphPaths) { 439 const SkTypeface* typeface = fFont.getTypeface(); 440 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, 2 + styleDataCount); 441 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0; 442 reinterpret_cast<uint32_t&>(builder[1]) = styleDataCount; 443 if (styleDataCount) { 444 write_style_key(&builder[2], fStyle); 445 } 446 } else { 447 SkGlyphCache* glyphCache = this->getGlyphCache(); 448 const SkTypeface* typeface = glyphCache->getScalerContext()->getTypeface(); 449 const SkDescriptor* desc = &glyphCache->getDescriptor(); 450 int descDataCount = (desc->getLength() + 3) / 4; 451 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, 452 2 + styleDataCount + descDataCount); 453 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0; 454 reinterpret_cast<uint32_t&>(builder[1]) = styleDataCount | (descDataCount << 16); 455 if (styleDataCount) { 456 write_style_key(&builder[2], fStyle); 457 } 458 memcpy(&builder[2 + styleDataCount], desc, desc->getLength()); 459 } 460 } 461} 462 463GrStencilAndCoverTextContext::TextRun::~TextRun() { 464 this->releaseGlyphCache(); 465} 466 467void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t byteLength, 468 SkScalar x, SkScalar y) { 469 SkASSERT(byteLength == 0 || text != nullptr); 470 471 SkGlyphCache* glyphCache = this->getGlyphCache(); 472 SkPaint::GlyphCacheProc glyphCacheProc = fFont.getGlyphCacheProc(true); 473 474 fTotalGlyphCount = fFont.countText(text, byteLength); 475 fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType, 476 fTotalGlyphCount)); 477 478 const char* stop = text + byteLength; 479 480 // Measure first if needed. 481 if (fFont.getTextAlign() != SkPaint::kLeft_Align) { 482 SkScalar stopX = 0; 483 SkScalar stopY = 0; 484 485 const char* textPtr = text; 486 while (textPtr < stop) { 487 // We don't need x, y here, since all subpixel variants will have the 488 // same advance. 489 const SkGlyph& glyph = glyphCacheProc(glyphCache, &textPtr); 490 491 stopX += SkFloatToScalar(glyph.fAdvanceX); 492 stopY += SkFloatToScalar(glyph.fAdvanceY); 493 } 494 SkASSERT(textPtr == stop); 495 496 SkScalar alignX = stopX * fTextRatio; 497 SkScalar alignY = stopY * fTextRatio; 498 499 if (fFont.getTextAlign() == SkPaint::kCenter_Align) { 500 alignX = SkScalarHalf(alignX); 501 alignY = SkScalarHalf(alignY); 502 } 503 504 x -= alignX; 505 y -= alignY; 506 } 507 508 SkAutoKern autokern; 509 510 FallbackBlobBuilder fallback; 511 while (text < stop) { 512 const SkGlyph& glyph = glyphCacheProc(glyphCache, &text); 513 x += autokern.adjust(glyph) * fTextRatio; 514 if (glyph.fWidth) { 515 this->appendGlyph(glyph, SkPoint::Make(x, y), &fallback); 516 } 517 518 x += SkFloatToScalar(glyph.fAdvanceX) * fTextRatio; 519 y += SkFloatToScalar(glyph.fAdvanceY) * fTextRatio; 520 } 521 522 fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount)); 523} 524 525void GrStencilAndCoverTextContext::TextRun::setPosText(const char text[], size_t byteLength, 526 const SkScalar pos[], int scalarsPerPosition, 527 const SkPoint& offset) { 528 SkASSERT(byteLength == 0 || text != nullptr); 529 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); 530 531 SkGlyphCache* glyphCache = this->getGlyphCache(); 532 SkPaint::GlyphCacheProc glyphCacheProc = fFont.getGlyphCacheProc(true); 533 534 fTotalGlyphCount = fFont.countText(text, byteLength); 535 fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType, 536 fTotalGlyphCount)); 537 538 const char* stop = text + byteLength; 539 540 SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition); 541 SkTextAlignProc alignProc(fFont.getTextAlign()); 542 FallbackBlobBuilder fallback; 543 while (text < stop) { 544 const SkGlyph& glyph = glyphCacheProc(glyphCache, &text); 545 if (glyph.fWidth) { 546 SkPoint tmsLoc; 547 tmsProc(pos, &tmsLoc); 548 SkPoint loc; 549 alignProc(tmsLoc, glyph, &loc); 550 551 this->appendGlyph(glyph, loc, &fallback); 552 } 553 pos += scalarsPerPosition; 554 } 555 556 fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount)); 557} 558 559GrPathRange* GrStencilAndCoverTextContext::TextRun::createGlyphs(GrContext* ctx) const { 560 GrPathRange* glyphs = static_cast<GrPathRange*>( 561 ctx->resourceProvider()->findAndRefResourceByUniqueKey(fGlyphPathsKey)); 562 if (nullptr == glyphs) { 563 if (fUsingRawGlyphPaths) { 564 SkScalerContextEffects noeffects; 565 glyphs = ctx->resourceProvider()->createGlyphs(fFont.getTypeface(), noeffects, 566 nullptr, fStyle); 567 } else { 568 SkGlyphCache* cache = this->getGlyphCache(); 569 glyphs = ctx->resourceProvider()->createGlyphs(cache->getScalerContext()->getTypeface(), 570 cache->getScalerContext()->getEffects(), 571 &cache->getDescriptor(), 572 fStyle); 573 } 574 ctx->resourceProvider()->assignUniqueKeyToResource(fGlyphPathsKey, glyphs); 575 } 576 return glyphs; 577} 578 579inline void GrStencilAndCoverTextContext::TextRun::appendGlyph(const SkGlyph& glyph, 580 const SkPoint& pos, 581 FallbackBlobBuilder* fallback) { 582 // Stick the glyphs we can't draw into the fallback text blob. 583 if (SkMask::kARGB32_Format == glyph.fMaskFormat) { 584 if (!fallback->isInitialized()) { 585 fallback->init(fFont, fTextRatio); 586 } 587 fallback->appendGlyph(glyph.getGlyphID(), pos); 588 } else { 589 fInstanceData->append(glyph.getGlyphID(), fTextInverseRatio * pos.x(), 590 fTextInverseRatio * pos.y()); 591 } 592} 593 594void GrStencilAndCoverTextContext::TextRun::draw(GrContext* ctx, 595 GrDrawContext* drawContext, 596 const GrPaint& grPaint, 597 const GrClip& clip, 598 GrColor color, 599 const SkMatrix& viewMatrix, 600 const SkSurfaceProps& props, 601 SkScalar x, SkScalar y, 602 const SkIRect& clipBounds, 603 GrAtlasTextContext* fallbackTextContext, 604 const SkPaint& originalSkPaint) const { 605 SkASSERT(fInstanceData); 606 SkASSERT(drawContext->isStencilBufferMultisampled() || !fFont.isAntiAlias()); 607 608 if (fInstanceData->count()) { 609 static constexpr GrUserStencilSettings kCoverPass( 610 GrUserStencilSettings::StaticInit< 611 0x0000, 612 GrUserStencilTest::kNotEqual, // Stencil pass accounts for clip. 613 0xffff, 614 GrUserStencilOp::kZero, 615 GrUserStencilOp::kKeep, 616 0xffff>() 617 ); 618 619 SkAutoTUnref<GrPathRange> glyphs(this->createGlyphs(ctx)); 620 if (fLastDrawnGlyphsID != glyphs->getUniqueID()) { 621 // Either this is the first draw or the glyphs object was purged since last draw. 622 glyphs->loadPathsIfNeeded(fInstanceData->indices(), fInstanceData->count()); 623 fLastDrawnGlyphsID = glyphs->getUniqueID(); 624 } 625 626 // Don't compute a bounding box. For dst copy texture, we'll opt instead for it to just copy 627 // the entire dst. Realistically this is a moot point, because any context that supports 628 // NV_path_rendering will also support NV_blend_equation_advanced. 629 // For clipping we'll just skip any optimizations based on the bounds. This does, however, 630 // hurt batching. 631 const SkRect bounds = SkRect::MakeIWH(drawContext->width(), drawContext->height()); 632 633 SkAutoTUnref<GrDrawBatch> batch( 634 GrDrawPathRangeBatch::Create(viewMatrix, fTextRatio, fTextInverseRatio * x, 635 fTextInverseRatio * y, color, 636 GrPathRendering::kWinding_FillType, glyphs, fInstanceData, 637 bounds)); 638 639 GrPipelineBuilder pipelineBuilder(grPaint); 640 pipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, fFont.isAntiAlias()); 641 pipelineBuilder.setUserStencil(&kCoverPass); 642 643 drawContext->drawBatch(pipelineBuilder, clip, batch); 644 } 645 646 if (fFallbackTextBlob) { 647 SkPaint fallbackSkPaint(originalSkPaint); 648 fStyle.strokeRec().applyToPaint(&fallbackSkPaint); 649 if (!fStyle.isSimpleFill()) { 650 fallbackSkPaint.setStrokeWidth(fStyle.strokeRec().getWidth() * fTextRatio); 651 } 652 653 fallbackTextContext->drawTextBlob(ctx, drawContext, clip, fallbackSkPaint, viewMatrix, 654 props, fFallbackTextBlob, x, y, nullptr, clipBounds); 655 } 656} 657 658SkGlyphCache* GrStencilAndCoverTextContext::TextRun::getGlyphCache() const { 659 if (!fDetachedGlyphCache) { 660 fDetachedGlyphCache = fFont.detachCache(nullptr, SkPaint::kNone_ScalerContextFlags, 661 nullptr); 662 } 663 return fDetachedGlyphCache; 664} 665 666 667void GrStencilAndCoverTextContext::TextRun::releaseGlyphCache() const { 668 if (fDetachedGlyphCache) { 669 SkGlyphCache::AttachCache(fDetachedGlyphCache); 670 fDetachedGlyphCache = nullptr; 671 } 672} 673 674size_t GrStencilAndCoverTextContext::TextRun::computeSizeInCache() const { 675 size_t size = sizeof(TextRun) + fGlyphPathsKey.size(); 676 // The instance data always reserves enough space for every glyph. 677 size += (fTotalGlyphCount + fFallbackGlyphCount) * (sizeof(uint16_t) + 2 * sizeof(float)); 678 if (fInstanceData) { 679 size += sizeof(InstanceData); 680 } 681 if (fFallbackTextBlob) { 682 size += sizeof(SkTextBlob); 683 } 684 return size; 685} 686 687//////////////////////////////////////////////////////////////////////////////////////////////////// 688 689void GrStencilAndCoverTextContext::FallbackBlobBuilder::init(const SkPaint& font, 690 SkScalar textRatio) { 691 SkASSERT(!this->isInitialized()); 692 fBuilder.reset(new SkTextBlobBuilder); 693 fFont = font; 694 fFont.setTextAlign(SkPaint::kLeft_Align); // The glyph positions will already account for align. 695 fFont.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 696 // No need for subpixel positioning with bitmap glyphs. TODO: revisit if non-bitmap color glyphs 697 // show up and https://code.google.com/p/skia/issues/detail?id=4408 gets resolved. 698 fFont.setSubpixelText(false); 699 fFont.setTextSize(fFont.getTextSize() * textRatio); 700 fBuffIdx = 0; 701} 702 703void GrStencilAndCoverTextContext::FallbackBlobBuilder::appendGlyph(uint16_t glyphId, 704 const SkPoint& pos) { 705 SkASSERT(this->isInitialized()); 706 if (fBuffIdx >= kWriteBufferSize) { 707 this->flush(); 708 } 709 fGlyphIds[fBuffIdx] = glyphId; 710 fPositions[fBuffIdx] = pos; 711 fBuffIdx++; 712 fCount++; 713} 714 715void GrStencilAndCoverTextContext::FallbackBlobBuilder::flush() { 716 SkASSERT(this->isInitialized()); 717 SkASSERT(fBuffIdx <= kWriteBufferSize); 718 if (!fBuffIdx) { 719 return; 720 } 721 // This will automatically merge with previous runs since we use the same font. 722 const SkTextBlobBuilder::RunBuffer& buff = fBuilder->allocRunPos(fFont, fBuffIdx); 723 memcpy(buff.glyphs, fGlyphIds, fBuffIdx * sizeof(uint16_t)); 724 memcpy(buff.pos, fPositions[0].asScalars(), fBuffIdx * 2 * sizeof(SkScalar)); 725 fBuffIdx = 0; 726} 727 728const SkTextBlob* GrStencilAndCoverTextContext::FallbackBlobBuilder::buildIfNeeded(int *count) { 729 *count = fCount; 730 if (fCount) { 731 this->flush(); 732 return fBuilder->build(); 733 } 734 return nullptr; 735} 736