Home | History | Annotate | Download | only in text
      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 
     18 GrAtlasTextContext::GrAtlasTextContext()
     19     : fDistanceAdjustTable(new GrDistanceFieldAdjustTable) {
     20 }
     21 
     22 
     23 GrAtlasTextContext* GrAtlasTextContext::Create() {
     24     return new GrAtlasTextContext();
     25 }
     26 
     27 bool 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 
     35 GrColor 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.
     59 bool 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 
     69 void 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 
    162 void 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 
    251 inline GrAtlasTextBlob*
    252 GrAtlasTextContext::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 
    277 inline GrAtlasTextBlob*
    278 GrAtlasTextContext::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 
    302 void 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 
    329 void 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 
    363 DRAW_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