1 /* 2 * Copyright 2015 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 #include "GrAtlasTextContext.h" 8 #include "GrContext.h" 9 #include "GrRenderTargetContext.h" 10 #include "GrTextBlobCache.h" 11 #include "SkDraw.h" 12 #include "SkDrawFilter.h" 13 #include "SkGr.h" 14 #include "ops/GrMeshDrawOp.h" 15 16 GrAtlasTextContext::GrAtlasTextContext() 17 : fDistanceAdjustTable(new GrDistanceFieldAdjustTable) { 18 } 19 20 GrAtlasTextContext* GrAtlasTextContext::Create() { 21 return new GrAtlasTextContext(); 22 } 23 24 bool GrAtlasTextContext::canDraw(const SkPaint& skPaint, 25 const SkMatrix& viewMatrix, 26 const SkSurfaceProps& props, 27 const GrShaderCaps& shaderCaps) { 28 return GrTextUtils::CanDrawAsDistanceFields(skPaint, viewMatrix, props, shaderCaps) || 29 !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix); 30 } 31 32 SkColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) { 33 SkColor canonicalColor = paint.computeLuminanceColor(); 34 if (lcd) { 35 // This is the correct computation, but there are tons of cases where LCD can be overridden. 36 // For now we just regenerate if any run in a textblob has LCD. 37 // TODO figure out where all of these overrides are and see if we can incorporate that logic 38 // at a higher level *OR* use sRGB 39 SkASSERT(false); 40 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor); 41 } else { 42 // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have 43 // gamma corrected masks anyways, nor color 44 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor), 45 SkColorGetG(canonicalColor), 46 SkColorGetB(canonicalColor)); 47 // reduce to our finite number of bits 48 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum)); 49 } 50 return canonicalColor; 51 } 52 53 uint32_t GrAtlasTextContext::ComputeScalerContextFlags(GrRenderTargetContext* rtc) { 54 // If we're doing gamma-correct rendering, then we can disable the gamma hacks. 55 // Otherwise, leave them on. In either case, we still want the contrast boost: 56 if (rtc->isGammaCorrect()) { 57 return SkPaint::kBoostContrast_ScalerContextFlag; 58 } else { 59 return SkPaint::kFakeGammaAndBoostContrast_ScalerContextFlags; 60 } 61 } 62 63 // TODO if this function ever shows up in profiling, then we can compute this value when the 64 // textblob is being built and cache it. However, for the time being textblobs mostly only have 1 65 // run so this is not a big deal to compute here. 66 bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) { 67 SkTextBlobRunIterator it(blob); 68 for (; !it.done(); it.next()) { 69 if (it.isLCD()) { 70 return true; 71 } 72 } 73 return false; 74 } 75 76 void GrAtlasTextContext::drawTextBlob(GrContext* context, GrRenderTargetContext* rtc, 77 const GrClip& clip, const SkPaint& skPaint, 78 const SkMatrix& viewMatrix, 79 const SkSurfaceProps& props, const SkTextBlob* blob, 80 SkScalar x, SkScalar y, 81 SkDrawFilter* drawFilter, const SkIRect& clipBounds) { 82 // If we have been abandoned, then don't draw 83 if (context->abandoned()) { 84 return; 85 } 86 87 sk_sp<GrAtlasTextBlob> cacheBlob; 88 SkMaskFilter::BlurRec blurRec; 89 GrAtlasTextBlob::Key key; 90 // It might be worth caching these things, but its not clear at this time 91 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here 92 const SkMaskFilter* mf = skPaint.getMaskFilter(); 93 bool canCache = !(skPaint.getPathEffect() || 94 (mf && !mf->asABlur(&blurRec)) || 95 drawFilter); 96 uint32_t scalerContextFlags = ComputeScalerContextFlags(rtc); 97 98 GrTextBlobCache* cache = context->getTextBlobCache(); 99 if (canCache) { 100 bool hasLCD = HasLCD(blob); 101 102 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry 103 SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() : 104 kUnknown_SkPixelGeometry; 105 106 // TODO we want to figure out a way to be able to use the canonical color on LCD text, 107 // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to 108 // ensure we always match the same key 109 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT : 110 ComputeCanonicalColor(skPaint, hasLCD); 111 112 key.fPixelGeometry = pixelGeometry; 113 key.fUniqueID = blob->uniqueID(); 114 key.fStyle = skPaint.getStyle(); 115 key.fHasBlur = SkToBool(mf); 116 key.fCanonicalColor = canonicalColor; 117 key.fScalerContextFlags = scalerContextFlags; 118 cacheBlob = cache->find(key); 119 } 120 121 GrTextUtils::Paint paint(&skPaint, rtc->getColorSpace(), rtc->getColorXformFromSRGB()); 122 if (cacheBlob) { 123 if (cacheBlob->mustRegenerate(paint, blurRec, viewMatrix, x, y)) { 124 // We have to remake the blob because changes may invalidate our masks. 125 // TODO we could probably get away reuse most of the time if the pointer is unique, 126 // but we'd have to clear the subrun information 127 cache->remove(cacheBlob.get()); 128 cacheBlob = cache->makeCachedBlob(blob, key, blurRec, skPaint); 129 RegenerateTextBlob(cacheBlob.get(), context->getAtlasGlyphCache(), 130 *context->caps()->shaderCaps(), paint, scalerContextFlags, 131 viewMatrix, props, blob, x, y, drawFilter); 132 } else { 133 cache->makeMRU(cacheBlob.get()); 134 135 if (CACHE_SANITY_CHECK) { 136 int glyphCount = 0; 137 int runCount = 0; 138 GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob); 139 sk_sp<GrAtlasTextBlob> sanityBlob(cache->makeBlob(glyphCount, runCount)); 140 sanityBlob->setupKey(key, blurRec, skPaint); 141 RegenerateTextBlob(sanityBlob.get(), context->getAtlasGlyphCache(), 142 *context->caps()->shaderCaps(), paint, scalerContextFlags, 143 viewMatrix, props, blob, x, y, drawFilter); 144 GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob); 145 } 146 } 147 } else { 148 if (canCache) { 149 cacheBlob = cache->makeCachedBlob(blob, key, blurRec, skPaint); 150 } else { 151 cacheBlob = cache->makeBlob(blob); 152 } 153 RegenerateTextBlob(cacheBlob.get(), context->getAtlasGlyphCache(), 154 *context->caps()->shaderCaps(), paint, scalerContextFlags, viewMatrix, 155 props, blob, x, y, drawFilter); 156 } 157 158 cacheBlob->flushCached(context, rtc, blob, props, fDistanceAdjustTable.get(), paint, drawFilter, 159 clip, viewMatrix, clipBounds, x, y); 160 } 161 162 void GrAtlasTextContext::RegenerateTextBlob(GrAtlasTextBlob* cacheBlob, 163 GrAtlasGlyphCache* fontCache, 164 const GrShaderCaps& shaderCaps, 165 const GrTextUtils::Paint& paint, 166 uint32_t scalerContextFlags, const SkMatrix& viewMatrix, 167 const SkSurfaceProps& props, const SkTextBlob* blob, 168 SkScalar x, SkScalar y, SkDrawFilter* drawFilter) { 169 cacheBlob->initReusableBlob(paint.luminanceColor(), viewMatrix, x, y); 170 171 // Regenerate textblob 172 SkTextBlobRunIterator it(blob); 173 GrTextUtils::RunPaint runPaint(&paint, drawFilter, props); 174 for (int run = 0; !it.done(); it.next(), run++) { 175 int glyphCount = it.glyphCount(); 176 size_t textLen = glyphCount * sizeof(uint16_t); 177 const SkPoint& offset = it.offset(); 178 cacheBlob->push_back_run(run); 179 if (!runPaint.modifyForRun(it)) { 180 continue; 181 } 182 if (GrTextUtils::CanDrawAsDistanceFields(runPaint, viewMatrix, props, shaderCaps)) { 183 switch (it.positioning()) { 184 case SkTextBlob::kDefault_Positioning: { 185 GrTextUtils::DrawDFText(cacheBlob, run, fontCache, props, runPaint, 186 scalerContextFlags, viewMatrix, 187 (const char*)it.glyphs(), textLen, x + offset.x(), 188 y + offset.y()); 189 break; 190 } 191 case SkTextBlob::kHorizontal_Positioning: { 192 SkPoint dfOffset = SkPoint::Make(x, y + offset.y()); 193 GrTextUtils::DrawDFPosText( 194 cacheBlob, run, fontCache, props, runPaint, scalerContextFlags, 195 viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 1, dfOffset); 196 break; 197 } 198 case SkTextBlob::kFull_Positioning: { 199 SkPoint dfOffset = SkPoint::Make(x, y); 200 GrTextUtils::DrawDFPosText( 201 cacheBlob, run, fontCache, props, runPaint, scalerContextFlags, 202 viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 2, dfOffset); 203 break; 204 } 205 } 206 } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) { 207 cacheBlob->setRunDrawAsPaths(run); 208 } else { 209 switch (it.positioning()) { 210 case SkTextBlob::kDefault_Positioning: 211 GrTextUtils::DrawBmpText(cacheBlob, run, fontCache, props, runPaint, 212 scalerContextFlags, viewMatrix, 213 (const char*)it.glyphs(), textLen, x + offset.x(), 214 y + offset.y()); 215 break; 216 case SkTextBlob::kHorizontal_Positioning: 217 GrTextUtils::DrawBmpPosText(cacheBlob, run, fontCache, props, runPaint, 218 scalerContextFlags, viewMatrix, 219 (const char*)it.glyphs(), textLen, it.pos(), 1, 220 SkPoint::Make(x, y + offset.y())); 221 break; 222 case SkTextBlob::kFull_Positioning: 223 GrTextUtils::DrawBmpPosText(cacheBlob, run, fontCache, props, runPaint, 224 scalerContextFlags, viewMatrix, 225 (const char*)it.glyphs(), textLen, it.pos(), 2, 226 SkPoint::Make(x, y)); 227 break; 228 } 229 } 230 } 231 } 232 233 inline sk_sp<GrAtlasTextBlob> 234 GrAtlasTextContext::MakeDrawTextBlob(GrTextBlobCache* blobCache, 235 GrAtlasGlyphCache* fontCache, 236 const GrShaderCaps& shaderCaps, 237 const GrTextUtils::Paint& paint, 238 uint32_t scalerContextFlags, 239 const SkMatrix& viewMatrix, 240 const SkSurfaceProps& props, 241 const char text[], size_t byteLength, 242 SkScalar x, SkScalar y) { 243 int glyphCount = paint.skPaint().countText(text, byteLength); 244 245 sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1); 246 blob->initThrowawayBlob(viewMatrix, x, y); 247 248 if (GrTextUtils::CanDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps)) { 249 GrTextUtils::DrawDFText(blob.get(), 0, fontCache, props, paint, scalerContextFlags, 250 viewMatrix, text, byteLength, x, y); 251 } else { 252 GrTextUtils::DrawBmpText(blob.get(), 0, fontCache, props, paint, scalerContextFlags, 253 viewMatrix, text, byteLength, x, y); 254 } 255 return blob; 256 } 257 258 inline sk_sp<GrAtlasTextBlob> 259 GrAtlasTextContext::MakeDrawPosTextBlob(GrTextBlobCache* blobCache, 260 GrAtlasGlyphCache* fontCache, 261 const GrShaderCaps& shaderCaps, 262 const GrTextUtils::Paint& paint, 263 uint32_t scalerContextFlags, 264 const SkMatrix& viewMatrix, 265 const SkSurfaceProps& props, 266 const char text[], size_t byteLength, 267 const SkScalar pos[], int scalarsPerPosition, const 268 SkPoint& offset) { 269 int glyphCount = paint.skPaint().countText(text, byteLength); 270 271 sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1); 272 blob->initThrowawayBlob(viewMatrix, offset.x(), offset.y()); 273 274 if (GrTextUtils::CanDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps)) { 275 GrTextUtils::DrawDFPosText(blob.get(), 0, fontCache, props, paint, scalerContextFlags, 276 viewMatrix, text, byteLength, pos, scalarsPerPosition, offset); 277 } else { 278 GrTextUtils::DrawBmpPosText(blob.get(), 0, fontCache, props, paint, scalerContextFlags, 279 viewMatrix, text, byteLength, pos, scalarsPerPosition, offset); 280 } 281 return blob; 282 } 283 284 void GrAtlasTextContext::drawText(GrContext* context, GrRenderTargetContext* rtc, 285 const GrClip& clip, const SkPaint& skPaint, 286 const SkMatrix& viewMatrix, const SkSurfaceProps& props, 287 const char text[], size_t byteLength, SkScalar x, SkScalar y, 288 const SkIRect& regionClipBounds) { 289 if (context->abandoned()) { 290 return; 291 } 292 GrTextUtils::Paint paint(&skPaint, rtc->getColorSpace(), rtc->getColorXformFromSRGB()); 293 if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) { 294 sk_sp<GrAtlasTextBlob> blob( 295 MakeDrawTextBlob(context->getTextBlobCache(), context->getAtlasGlyphCache(), 296 *context->caps()->shaderCaps(), 297 paint, ComputeScalerContextFlags(rtc), 298 viewMatrix, props, 299 text, byteLength, x, y)); 300 blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), paint, clip, 301 viewMatrix, regionClipBounds, x, y); 302 return; 303 } 304 305 // fall back to drawing as a path 306 GrTextUtils::DrawTextAsPath(context, rtc, clip, paint, viewMatrix, text, byteLength, x, y, 307 regionClipBounds); 308 } 309 310 void GrAtlasTextContext::drawPosText(GrContext* context, GrRenderTargetContext* rtc, 311 const GrClip& clip, const SkPaint& skPaint, 312 const SkMatrix& viewMatrix, const SkSurfaceProps& props, 313 const char text[], size_t byteLength, const SkScalar pos[], 314 int scalarsPerPosition, const SkPoint& offset, 315 const SkIRect& regionClipBounds) { 316 GrTextUtils::Paint paint(&skPaint, rtc->getColorSpace(), rtc->getColorXformFromSRGB()); 317 if (context->abandoned()) { 318 return; 319 } else if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) { 320 sk_sp<GrAtlasTextBlob> blob( 321 MakeDrawPosTextBlob(context->getTextBlobCache(), context->getAtlasGlyphCache(), 322 *context->caps()->shaderCaps(), 323 paint, ComputeScalerContextFlags(rtc), 324 viewMatrix, props, 325 text, byteLength, 326 pos, scalarsPerPosition, 327 offset)); 328 blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), paint, clip, 329 viewMatrix, regionClipBounds, offset.fX, offset.fY); 330 return; 331 } 332 333 // fall back to drawing as a path 334 GrTextUtils::DrawPosTextAsPath(context, rtc, props, clip, paint, viewMatrix, text, byteLength, 335 pos, scalarsPerPosition, offset, regionClipBounds); 336 } 337 338 /////////////////////////////////////////////////////////////////////////////////////////////////// 339 340 #if GR_TEST_UTILS 341 342 GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) { 343 static uint32_t gContextID = SK_InvalidGenID; 344 static GrAtlasTextContext* gTextContext = nullptr; 345 static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType); 346 347 if (context->uniqueID() != gContextID) { 348 gContextID = context->uniqueID(); 349 delete gTextContext; 350 351 gTextContext = GrAtlasTextContext::Create(); 352 } 353 354 // Setup dummy SkPaint / GrPaint / GrRenderTargetContext 355 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContext( 356 SkBackingFit::kApprox, 1024, 1024, kRGBA_8888_GrPixelConfig, nullptr)); 357 358 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); 359 360 // Because we the GrTextUtils::Paint requires an SkPaint for font info, we ignore the GrPaint 361 // param. 362 SkPaint skPaint; 363 skPaint.setColor(random->nextU()); 364 skPaint.setLCDRenderText(random->nextBool()); 365 skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool()); 366 skPaint.setSubpixelText(random->nextBool()); 367 GrTextUtils::Paint utilsPaint(&skPaint, rtc->getColorSpace(), rtc->getColorXformFromSRGB()); 368 369 const char* text = "The quick brown fox jumps over the lazy dog."; 370 int textLen = (int)strlen(text); 371 372 // create some random x/y offsets, including negative offsets 373 static const int kMaxTrans = 1024; 374 int xPos = (random->nextU() % 2) * 2 - 1; 375 int yPos = (random->nextU() % 2) * 2 - 1; 376 int xInt = (random->nextU() % kMaxTrans) * xPos; 377 int yInt = (random->nextU() % kMaxTrans) * yPos; 378 SkScalar x = SkIntToScalar(xInt); 379 SkScalar y = SkIntToScalar(yInt); 380 381 // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to 382 // test the text op with this unit test, that is okay. 383 sk_sp<GrAtlasTextBlob> blob(GrAtlasTextContext::MakeDrawTextBlob( 384 context->getTextBlobCache(), context->getAtlasGlyphCache(), 385 *context->caps()->shaderCaps(), utilsPaint, 386 GrAtlasTextContext::kTextBlobOpScalerContextFlags, viewMatrix, gSurfaceProps, text, 387 static_cast<size_t>(textLen), x, y)); 388 389 return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, utilsPaint, gSurfaceProps, 390 gTextContext->dfAdjustTable(), context->getAtlasGlyphCache(), 391 rtc.get()); 392 } 393 394 #endif 395