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