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