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 #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