diff options
author | Herb Derby <herb@google.com> | 2018-05-24 14:39:01 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-05-24 20:41:08 +0000 |
commit | 26cbe5130aa9839b5429b9507363ce2474091808 (patch) | |
tree | 5d10df1e2a768bf31af6ccbd2fd636f44f7c98b6 /src/gpu/text/GrTextContext.cpp | |
parent | 4a0ad501e4919e06bcf7a7ef4adec8f28818946b (diff) |
Rename GrAtlasTextContext -> GrTextContext
Change-Id: I309b39425afc9b45095241eeb299096bc426afed
Reviewed-on: https://skia-review.googlesource.com/130029
Reviewed-by: Herb Derby <herb@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Herb Derby <herb@google.com>
Diffstat (limited to 'src/gpu/text/GrTextContext.cpp')
-rw-r--r-- | src/gpu/text/GrTextContext.cpp | 986 |
1 files changed, 986 insertions, 0 deletions
diff --git a/src/gpu/text/GrTextContext.cpp b/src/gpu/text/GrTextContext.cpp new file mode 100644 index 0000000000..dfc6898929 --- /dev/null +++ b/src/gpu/text/GrTextContext.cpp @@ -0,0 +1,986 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "GrTextContext.h" +#include "GrCaps.h" +#include "GrContext.h" +#include "GrContextPriv.h" +#include "GrSDFMaskFilter.h" +#include "GrTextBlobCache.h" +#include "SkDistanceFieldGen.h" +#include "SkDraw.h" +#include "SkDrawFilter.h" +#include "SkDrawProcs.h" +#include "SkFindAndPlaceGlyph.h" +#include "SkGr.h" +#include "SkGraphics.h" +#include "SkMakeUnique.h" +#include "SkMaskFilterBase.h" +#include "SkPaintPriv.h" +#include "SkTextMapStateProc.h" + +#include "ops/GrMeshDrawOp.h" + +// DF sizes and thresholds for usage of the small and medium sizes. For example, above +// kSmallDFFontLimit we will use the medium size. The large size is used up until the size at +// which we switch over to drawing as paths as controlled by Options. +static const int kSmallDFFontSize = 32; +static const int kSmallDFFontLimit = 32; +static const int kMediumDFFontSize = 72; +static const int kMediumDFFontLimit = 72; +static const int kLargeDFFontSize = 162; + +static const int kDefaultMinDistanceFieldFontSize = 18; +#ifdef SK_BUILD_FOR_ANDROID +static const int kDefaultMaxDistanceFieldFontSize = 384; +#else +static const int kDefaultMaxDistanceFieldFontSize = 2 * kLargeDFFontSize; +#endif + +GrTextContext::GrTextContext(const Options& options) + : fDistanceAdjustTable(new GrDistanceFieldAdjustTable), fOptions(options) { + SanitizeOptions(&fOptions); +} + +std::unique_ptr<GrTextContext> GrTextContext::Make(const Options& options) { + return std::unique_ptr<GrTextContext>(new GrTextContext(options)); +} + +SkColor GrTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) { + SkColor canonicalColor = paint.computeLuminanceColor(); + if (lcd) { + // This is the correct computation, but there are tons of cases where LCD can be overridden. + // For now we just regenerate if any run in a textblob has LCD. + // TODO figure out where all of these overrides are and see if we can incorporate that logic + // at a higher level *OR* use sRGB + SkASSERT(false); + //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor); + } else { + // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have + // gamma corrected masks anyways, nor color + U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor), + SkColorGetG(canonicalColor), + SkColorGetB(canonicalColor)); + // reduce to our finite number of bits + canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum)); + } + return canonicalColor; +} + +SkScalerContextFlags GrTextContext::ComputeScalerContextFlags( + const GrColorSpaceInfo& colorSpaceInfo) { + // If we're doing gamma-correct rendering, then we can disable the gamma hacks. + // Otherwise, leave them on. In either case, we still want the contrast boost: + if (colorSpaceInfo.isGammaCorrect()) { + return SkScalerContextFlags::kBoostContrast; + } else { + return SkScalerContextFlags::kFakeGammaAndBoostContrast; + } +} + +// TODO if this function ever shows up in profiling, then we can compute this value when the +// textblob is being built and cache it. However, for the time being textblobs mostly only have 1 +// run so this is not a big deal to compute here. +bool GrTextContext::HasLCD(const SkTextBlob* blob) { + SkTextBlobRunIterator it(blob); + for (; !it.done(); it.next()) { + if (it.isLCD()) { + return true; + } + } + return false; +} + +void GrTextContext::drawTextBlob(GrContext* context, GrTextUtils::Target* target, + const GrClip& clip, const SkPaint& skPaint, + const SkMatrix& viewMatrix, const SkSurfaceProps& props, + const SkTextBlob* blob, SkScalar x, SkScalar y, + SkDrawFilter* drawFilter, const SkIRect& clipBounds) { + // If we have been abandoned, then don't draw + if (context->contextPriv().abandoned()) { + return; + } + + sk_sp<GrAtlasTextBlob> cacheBlob; + SkMaskFilterBase::BlurRec blurRec; + GrAtlasTextBlob::Key key; + // It might be worth caching these things, but its not clear at this time + // TODO for animated mask filters, this will fill up our cache. We need a safeguard here + const SkMaskFilter* mf = skPaint.getMaskFilter(); + bool canCache = !(skPaint.getPathEffect() || + (mf && !as_MFB(mf)->asABlur(&blurRec)) || + drawFilter); + SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorSpaceInfo()); + + auto glyphCache = context->contextPriv().getGlyphCache(); + GrTextBlobCache* textBlobCache = context->contextPriv().getTextBlobCache(); + + if (canCache) { + bool hasLCD = HasLCD(blob); + + // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry + SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() : + kUnknown_SkPixelGeometry; + + // TODO we want to figure out a way to be able to use the canonical color on LCD text, + // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to + // ensure we always match the same key + GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT : + ComputeCanonicalColor(skPaint, hasLCD); + + key.fPixelGeometry = pixelGeometry; + key.fUniqueID = blob->uniqueID(); + key.fStyle = skPaint.getStyle(); + key.fHasBlur = SkToBool(mf); + key.fCanonicalColor = canonicalColor; + key.fScalerContextFlags = scalerContextFlags; + cacheBlob = textBlobCache->find(key); + } + + GrTextUtils::Paint paint(&skPaint, &target->colorSpaceInfo()); + if (cacheBlob) { + if (cacheBlob->mustRegenerate(paint, blurRec, viewMatrix, x, y)) { + // We have to remake the blob because changes may invalidate our masks. + // TODO we could probably get away reuse most of the time if the pointer is unique, + // but we'd have to clear the subrun information + textBlobCache->remove(cacheBlob.get()); + cacheBlob = textBlobCache->makeCachedBlob(blob, key, blurRec, skPaint); + this->regenerateTextBlob(cacheBlob.get(), glyphCache, + *context->contextPriv().caps()->shaderCaps(), paint, + scalerContextFlags, viewMatrix, props, blob, x, y, drawFilter); + } else { + textBlobCache->makeMRU(cacheBlob.get()); + + if (CACHE_SANITY_CHECK) { + int glyphCount = 0; + int runCount = 0; + GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob); + sk_sp<GrAtlasTextBlob> sanityBlob(textBlobCache->makeBlob(glyphCount, runCount)); + sanityBlob->setupKey(key, blurRec, skPaint); + this->regenerateTextBlob( + sanityBlob.get(), glyphCache, *context->contextPriv().caps()->shaderCaps(), + paint, scalerContextFlags, viewMatrix, props, blob, x, y, drawFilter); + GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob); + } + } + } else { + if (canCache) { + cacheBlob = textBlobCache->makeCachedBlob(blob, key, blurRec, skPaint); + } else { + cacheBlob = textBlobCache->makeBlob(blob); + } + this->regenerateTextBlob(cacheBlob.get(), glyphCache, + *context->contextPriv().caps()->shaderCaps(), paint, + scalerContextFlags, viewMatrix, props, blob, x, y, drawFilter); + } + + cacheBlob->flush(target, props, fDistanceAdjustTable.get(), paint, + clip, viewMatrix, clipBounds, x, y); +} + +void GrTextContext::regenerateTextBlob(GrAtlasTextBlob* cacheBlob, + GrGlyphCache* glyphCache, + const GrShaderCaps& shaderCaps, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, const SkTextBlob* blob, + SkScalar x, SkScalar y, + SkDrawFilter* drawFilter) const { + cacheBlob->initReusableBlob(paint.luminanceColor(), viewMatrix, x, y); + + // Regenerate textblob + SkTextBlobRunIterator it(blob); + GrTextUtils::RunPaint runPaint(&paint, drawFilter); + for (int run = 0; !it.done(); it.next(), run++) { + int glyphCount = it.glyphCount(); + size_t textLen = glyphCount * sizeof(uint16_t); + const SkPoint& offset = it.offset(); + cacheBlob->push_back_run(run); + if (!runPaint.modifyForRun([it](SkPaint* p) { it.applyFontToPaint(p); })) { + continue; + } + cacheBlob->setRunPaintFlags(run, runPaint.skPaint().getFlags()); + + if (CanDrawAsDistanceFields(runPaint, viewMatrix, props, + shaderCaps.supportsDistanceFieldText(), fOptions)) { + switch (it.positioning()) { + case SkTextBlob::kDefault_Positioning: { + this->drawDFText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags, + viewMatrix, (const char*)it.glyphs(), textLen, x + offset.x(), + y + offset.y()); + break; + } + case SkTextBlob::kHorizontal_Positioning: { + SkPoint dfOffset = SkPoint::Make(x, y + offset.y()); + this->drawDFPosText(cacheBlob, run, glyphCache, props, runPaint, + scalerContextFlags, viewMatrix, (const char*)it.glyphs(), + textLen, it.pos(), 1, dfOffset); + break; + } + case SkTextBlob::kFull_Positioning: { + SkPoint dfOffset = SkPoint::Make(x, y); + this->drawDFPosText(cacheBlob, run, glyphCache, props, runPaint, + scalerContextFlags, viewMatrix, (const char*)it.glyphs(), + textLen, it.pos(), 2, dfOffset); + break; + } + } + } else { + switch (it.positioning()) { + case SkTextBlob::kDefault_Positioning: + DrawBmpText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags, + viewMatrix, (const char*)it.glyphs(), textLen, x + offset.x(), + y + offset.y()); + break; + case SkTextBlob::kHorizontal_Positioning: + DrawBmpPosText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags, + viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 1, + SkPoint::Make(x, y + offset.y())); + break; + case SkTextBlob::kFull_Positioning: + DrawBmpPosText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags, + viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 2, + SkPoint::Make(x, y)); + break; + } + } + } +} + +inline sk_sp<GrAtlasTextBlob> +GrTextContext::makeDrawTextBlob(GrTextBlobCache* blobCache, + GrGlyphCache* glyphCache, + const GrShaderCaps& shaderCaps, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + const char text[], size_t byteLength, + SkScalar x, SkScalar y) const { + int glyphCount = paint.skPaint().countText(text, byteLength); + if (!glyphCount) { + return nullptr; + } + sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1); + blob->initThrowawayBlob(viewMatrix, x, y); + blob->setRunPaintFlags(0, paint.skPaint().getFlags()); + + if (CanDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps.supportsDistanceFieldText(), + fOptions)) { + this->drawDFText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix, + text, byteLength, x, y); + } else { + DrawBmpText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix, text, + byteLength, x, y); + } + return blob; +} + +inline sk_sp<GrAtlasTextBlob> +GrTextContext::makeDrawPosTextBlob(GrTextBlobCache* blobCache, + GrGlyphCache* glyphCache, + const GrShaderCaps& shaderCaps, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + const char text[], size_t byteLength, + const SkScalar pos[], int scalarsPerPosition, const + SkPoint& offset) const { + int glyphCount = paint.skPaint().countText(text, byteLength); + if (!glyphCount) { + return nullptr; + } + + sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1); + blob->initThrowawayBlob(viewMatrix, offset.x(), offset.y()); + blob->setRunPaintFlags(0, paint.skPaint().getFlags()); + + if (CanDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps.supportsDistanceFieldText(), + fOptions)) { + this->drawDFPosText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix, + text, byteLength, pos, scalarsPerPosition, offset); + } else { + DrawBmpPosText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix, + text, byteLength, pos, scalarsPerPosition, offset); + } + return blob; +} + +void GrTextContext::drawText(GrContext* context, GrTextUtils::Target* target, + const GrClip& clip, const SkPaint& skPaint, + const SkMatrix& viewMatrix, const SkSurfaceProps& props, + const char text[], size_t byteLength, SkScalar x, SkScalar y, + const SkIRect& regionClipBounds) { + if (context->contextPriv().abandoned()) { + return; + } + + auto glyphCache = context->contextPriv().getGlyphCache(); + auto textBlobCache = context->contextPriv().getTextBlobCache(); + + GrTextUtils::Paint paint(&skPaint, &target->colorSpaceInfo()); + sk_sp<GrAtlasTextBlob> blob(this->makeDrawTextBlob( + textBlobCache, glyphCache, *context->contextPriv().caps()->shaderCaps(), paint, + ComputeScalerContextFlags(target->colorSpaceInfo()), viewMatrix, props, text, + byteLength, x, y)); + if (blob) { + blob->flush(target, props, fDistanceAdjustTable.get(), paint, + clip, viewMatrix, regionClipBounds, x, y); + } +} + +void GrTextContext::drawPosText(GrContext* context, GrTextUtils::Target* target, + const GrClip& clip, const SkPaint& skPaint, + const SkMatrix& viewMatrix, const SkSurfaceProps& props, + const char text[], size_t byteLength, const SkScalar pos[], + int scalarsPerPosition, const SkPoint& offset, + const SkIRect& regionClipBounds) { + GrTextUtils::Paint paint(&skPaint, &target->colorSpaceInfo()); + if (context->contextPriv().abandoned()) { + return; + } + + auto glyphCache = context->contextPriv().getGlyphCache(); + auto textBlobCache = context->contextPriv().getTextBlobCache(); + + sk_sp<GrAtlasTextBlob> blob(this->makeDrawPosTextBlob( + textBlobCache, glyphCache, *context->contextPriv().caps()->shaderCaps(), paint, + ComputeScalerContextFlags(target->colorSpaceInfo()), viewMatrix, props, text, + byteLength, pos, scalarsPerPosition, offset)); + if (blob) { + blob->flush(target, props, fDistanceAdjustTable.get(), paint, + clip, viewMatrix, regionClipBounds, offset.fX, offset.fY); + } +} + +void GrTextContext::DrawBmpText(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, const SkSurfaceProps& props, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, const char text[], + size_t byteLength, SkScalar x, SkScalar y) { + SkASSERT(byteLength == 0 || text != nullptr); + + // nothing to draw + if (text == nullptr || byteLength == 0) { + return; + } + + // Ensure the blob is set for bitmaptext + blob->setHasBitmap(); + + if (SkDraw::ShouldDrawTextAsPaths(paint, viewMatrix)) { + DrawBmpTextAsPaths(blob, runIndex, glyphCache, props, paint, scalerContextFlags, viewMatrix, + text, byteLength, x, y); + return; + } + + sk_sp<GrTextStrike> currStrike; + auto cache = blob->setupCache(runIndex, props, scalerContextFlags, paint, &viewMatrix); + SkFindAndPlaceGlyph::ProcessText(paint.skPaint().getTextEncoding(), text, byteLength, {x, y}, + viewMatrix, paint.skPaint().getTextAlign(), cache.get(), + [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) { + position += rounding; + BmpAppendGlyph(blob, runIndex, glyphCache, &currStrike, + glyph, SkScalarFloorToScalar(position.fX), + SkScalarFloorToScalar(position.fY), + paint.filteredPremulColor(), cache.get(), + SK_Scalar1, false); + }); +} + +void GrTextContext::DrawBmpPosText(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, const SkSurfaceProps& props, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, const SkScalar pos[], + int scalarsPerPosition, const SkPoint& offset) { + SkASSERT(byteLength == 0 || text != nullptr); + SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); + + // nothing to draw + if (text == nullptr || byteLength == 0) { + return; + } + + // Ensure the blob is set for bitmaptext + blob->setHasBitmap(); + + if (SkDraw::ShouldDrawTextAsPaths(paint, viewMatrix)) { + DrawBmpPosTextAsPaths(blob, runIndex, glyphCache, props, paint, scalerContextFlags, + viewMatrix, text, byteLength, pos, scalarsPerPosition, offset); + return; + } + + sk_sp<GrTextStrike> currStrike; + auto cache = blob->setupCache(runIndex, props, scalerContextFlags, paint, &viewMatrix); + SkFindAndPlaceGlyph::ProcessPosText( + paint.skPaint().getTextEncoding(), text, byteLength, offset, viewMatrix, pos, + scalarsPerPosition, cache.get(), + [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) { + position += rounding; + BmpAppendGlyph(blob, runIndex, glyphCache, &currStrike, glyph, + SkScalarFloorToScalar(position.fX), + SkScalarFloorToScalar(position.fY), + paint.filteredPremulColor(), cache.get(), SK_Scalar1, false); + }); +} + +void GrTextContext::DrawBmpTextAsPaths(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, + const SkSurfaceProps& props, + const GrTextUtils::Paint& origPaint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, const char text[], + size_t byteLength, SkScalar x, SkScalar y) { + // nothing to draw + if (text == nullptr || byteLength == 0) { + return; + } + + // Temporarily jam in kFill, so we only ever ask for the raw outline from the cache. + SkPaint pathPaint(origPaint); + pathPaint.setStyle(SkPaint::kFill_Style); + pathPaint.setPathEffect(nullptr); + + GrTextUtils::PathTextIter iter(text, byteLength, pathPaint, true); + FallbackTextHelper fallbackTextHelper(viewMatrix, pathPaint, glyphCache, iter.getPathScale()); + + const SkGlyph* iterGlyph; + const SkPath* iterPath; + SkScalar xpos = 0; + const char* lastText = text; + while (iter.next(&iterGlyph, &iterPath, &xpos)) { + if (iterGlyph) { + SkPoint pos = SkPoint::Make(xpos + x, y); + fallbackTextHelper.appendText(*iterGlyph, iter.getText() - lastText, lastText, pos); + } else if (iterPath) { + blob->appendPathGlyph(runIndex, *iterPath, xpos + x, y, iter.getPathScale(), false); + } + lastText = iter.getText(); + } + + fallbackTextHelper.drawText(blob, runIndex, glyphCache, props, origPaint, scalerContextFlags); +} + +void GrTextContext::DrawBmpPosTextAsPaths(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, + const SkSurfaceProps& props, + const GrTextUtils::Paint& origPaint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, + const SkScalar pos[], int scalarsPerPosition, + const SkPoint& offset) { + SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); + + // nothing to draw + if (text == nullptr || byteLength == 0) { + return; + } + + // setup our std paint, in hopes of getting hits in the cache + SkPaint pathPaint(origPaint); + SkScalar matrixScale = pathPaint.setupForAsPaths(); + FallbackTextHelper fallbackTextHelper(viewMatrix, origPaint, glyphCache, matrixScale); + + // Temporarily jam in kFill, so we only ever ask for the raw outline from the cache. + pathPaint.setStyle(SkPaint::kFill_Style); + pathPaint.setPathEffect(nullptr); + + SkPaint::GlyphCacheProc glyphCacheProc = SkPaint::GetGlyphCacheProc(pathPaint.getTextEncoding(), + true); + auto cache = SkStrikeCache::FindOrCreateStrikeExclusive( + pathPaint, &props, SkScalerContextFlags::kFakeGammaAndBoostContrast, nullptr); + + const char* stop = text + byteLength; + const char* lastText = text; + SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition); + + while (text < stop) { + const SkGlyph& glyph = glyphCacheProc(cache.get(), &text); + if (glyph.fWidth) { + SkPoint loc; + tmsProc(pos, &loc); + if (SkMask::kARGB32_Format == glyph.fMaskFormat) { + fallbackTextHelper.appendText(glyph, text - lastText, lastText, loc); + } else { + const SkPath* path = cache->findPath(glyph); + if (path) { + blob->appendPathGlyph(runIndex, *path, loc.fX, loc.fY, matrixScale, false); + } + } + } + lastText = text; + pos += scalarsPerPosition; + } + + fallbackTextHelper.drawText(blob, runIndex, glyphCache, props, origPaint, scalerContextFlags); +} + +void GrTextContext::BmpAppendGlyph(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* grGlyphCache, + sk_sp<GrTextStrike>* strike, + const SkGlyph& skGlyph, SkScalar sx, SkScalar sy, + GrColor color, SkGlyphCache* skGlyphCache, + SkScalar textRatio, bool needsTransform) { + if (!*strike) { + *strike = grGlyphCache->getStrike(skGlyphCache); + } + + GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(), + skGlyph.getSubXFixed(), + skGlyph.getSubYFixed(), + GrGlyph::kCoverage_MaskStyle); + GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, skGlyphCache); + if (!glyph) { + return; + } + + SkASSERT(skGlyph.fWidth == glyph->width()); + SkASSERT(skGlyph.fHeight == glyph->height()); + + SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft); + SkScalar dy = SkIntToScalar(glyph->fBounds.fTop); + SkScalar width = SkIntToScalar(glyph->fBounds.width()); + SkScalar height = SkIntToScalar(glyph->fBounds.height()); + + dx *= textRatio; + dy *= textRatio; + width *= textRatio; + height *= textRatio; + + SkRect glyphRect = SkRect::MakeXYWH(sx + dx, sy + dy, width, height); + + blob->appendGlyph(runIndex, glyphRect, color, *strike, glyph, skGlyphCache, skGlyph, sx, sy, + textRatio, !needsTransform); +} + +void GrTextContext::SanitizeOptions(Options* options) { + if (options->fMaxDistanceFieldFontSize < 0.f) { + options->fMaxDistanceFieldFontSize = kDefaultMaxDistanceFieldFontSize; + } + if (options->fMinDistanceFieldFontSize < 0.f) { + options->fMinDistanceFieldFontSize = kDefaultMinDistanceFieldFontSize; + } +} + +bool GrTextContext::CanDrawAsDistanceFields(const SkPaint& skPaint, const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + bool contextSupportsDistanceFieldText, + const Options& options) { + if (!viewMatrix.hasPerspective()) { + SkScalar maxScale = viewMatrix.getMaxScale(); + SkScalar scaledTextSize = maxScale * skPaint.getTextSize(); + // Hinted text looks far better at small resolutions + // Scaling up beyond 2x yields undesireable artifacts + if (scaledTextSize < options.fMinDistanceFieldFontSize || + scaledTextSize > options.fMaxDistanceFieldFontSize) { + return false; + } + + bool useDFT = props.isUseDeviceIndependentFonts(); +#if SK_FORCE_DISTANCE_FIELD_TEXT + useDFT = true; +#endif + + if (!useDFT && scaledTextSize < kLargeDFFontSize) { + return false; + } + } + + // mask filters modify alpha, which doesn't translate well to distance + if (skPaint.getMaskFilter() || !contextSupportsDistanceFieldText) { + return false; + } + + // TODO: add some stroking support + if (skPaint.getStyle() != SkPaint::kFill_Style) { + return false; + } + + return true; +} + +void GrTextContext::InitDistanceFieldPaint(GrAtlasTextBlob* blob, + SkPaint* skPaint, + const SkMatrix& viewMatrix, + const Options& options, + SkScalar* textRatio, + SkScalerContextFlags* flags) { + SkScalar textSize = skPaint->getTextSize(); + SkScalar scaledTextSize = textSize; + + if (viewMatrix.hasPerspective()) { + // for perspective, we simply force to the medium size + // TODO: compute a size based on approximate screen area + scaledTextSize = kMediumDFFontLimit; + } else { + SkScalar maxScale = viewMatrix.getMaxScale(); + // if we have non-unity scale, we need to choose our base text size + // based on the SkPaint's text size multiplied by the max scale factor + // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? + if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { + scaledTextSize *= maxScale; + } + } + + // We have three sizes of distance field text, and within each size 'bucket' there is a floor + // and ceiling. A scale outside of this range would require regenerating the distance fields + SkScalar dfMaskScaleFloor; + SkScalar dfMaskScaleCeil; + if (scaledTextSize <= kSmallDFFontLimit) { + dfMaskScaleFloor = options.fMinDistanceFieldFontSize; + dfMaskScaleCeil = kSmallDFFontLimit; + *textRatio = textSize / kSmallDFFontSize; + skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize)); + } else if (scaledTextSize <= kMediumDFFontLimit) { + dfMaskScaleFloor = kSmallDFFontLimit; + dfMaskScaleCeil = kMediumDFFontLimit; + *textRatio = textSize / kMediumDFFontSize; + skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize)); + } else { + dfMaskScaleFloor = kMediumDFFontLimit; + dfMaskScaleCeil = options.fMaxDistanceFieldFontSize; + *textRatio = textSize / kLargeDFFontSize; + skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize)); + } + + // Because there can be multiple runs in the blob, we want the overall maxMinScale, and + // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale + // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can + // tolerate before we'd have to move to a large mip size. When we actually test these values + // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test + // against these values to decide if we can reuse or not(ie, will a given scale change our mip + // level) + SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil); + if (blob) { + blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize, + dfMaskScaleCeil / scaledTextSize); + } + + skPaint->setAntiAlias(true); + skPaint->setLCDRenderText(false); + skPaint->setAutohinted(false); + skPaint->setHinting(SkPaint::kNormal_Hinting); + skPaint->setSubpixelText(true); + + skPaint->setMaskFilter(GrSDFMaskFilter::Make()); + + // We apply the fake-gamma by altering the distance in the shader, so we ignore the + // passed-in scaler context flags. (It's only used when we fall-back to bitmap text). + *flags = SkScalerContextFlags::kNone; +} + +void GrTextContext::drawDFText(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, const SkSurfaceProps& props, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, const char text[], + size_t byteLength, SkScalar x, SkScalar y) const { + SkASSERT(byteLength == 0 || text != nullptr); + + // nothing to draw + if (text == nullptr || byteLength == 0) { + return; + } + + const SkPaint& skPaint = paint.skPaint(); + SkPaint::GlyphCacheProc glyphCacheProc = + SkPaint::GetGlyphCacheProc(skPaint.getTextEncoding(), true); + + SkTArray<SkScalar> positions; + + const char* textPtr = text; + SkScalar stopX = 0; + SkScalar stopY = 0; + + SkAutoDescriptor desc; + SkScalerContextEffects effects; + // We apply the fake-gamma by altering the distance in the shader, so we ignore the + // passed-in scaler context flags. (It's only used when we fall-back to bitmap text). + SkScalerContext::CreateDescriptorAndEffectsUsingPaint( + skPaint, &props, SkScalerContextFlags::kNone, nullptr, &desc, &effects); + auto typeface = SkPaintPriv::GetTypefaceOrDefault(skPaint); + + { + auto origPaintCache = + SkStrikeCache::FindOrCreateStrikeExclusive(*desc.getDesc(), effects, *typeface); + + const char* stop = text + byteLength; + while (textPtr < stop) { + // don't need x, y here, since all subpixel variants will have the + // same advance + const SkGlyph& glyph = glyphCacheProc(origPaintCache.get(), &textPtr); + + positions.push_back(stopX); + positions.push_back(stopY); + + stopX += SkFloatToScalar(glyph.fAdvanceX); + stopY += SkFloatToScalar(glyph.fAdvanceY); + } + SkASSERT(textPtr == stop); + } + + // now adjust starting point depending on alignment + SkScalar alignX = stopX; + SkScalar alignY = stopY; + if (skPaint.getTextAlign() == SkPaint::kCenter_Align) { + alignX = SkScalarHalf(alignX); + alignY = SkScalarHalf(alignY); + } else if (skPaint.getTextAlign() == SkPaint::kLeft_Align) { + alignX = 0; + alignY = 0; + } + x -= alignX; + y -= alignY; + SkPoint offset = SkPoint::Make(x, y); + + this->drawDFPosText(blob, runIndex, glyphCache, props, paint, scalerContextFlags, + viewMatrix, text, byteLength, positions.begin(), 2, offset); +} + +void GrTextContext::drawDFPosText(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, const SkSurfaceProps& props, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, const char text[], + size_t byteLength, const SkScalar pos[], + int scalarsPerPosition, const SkPoint& offset) const { + SkASSERT(byteLength == 0 || text != nullptr); + SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); + + // nothing to draw + if (text == nullptr || byteLength == 0) { + return; + } + + bool hasWCoord = viewMatrix.hasPerspective() || fOptions.fDistanceFieldVerticesAlwaysHaveW; + + // Setup distance field paint and text ratio + SkScalar textRatio; + SkPaint dfPaint(paint); + SkScalerContextFlags flags; + InitDistanceFieldPaint(blob, &dfPaint, viewMatrix, fOptions, &textRatio, &flags); + blob->setHasDistanceField(); + blob->setSubRunHasDistanceFields(runIndex, paint.skPaint().isLCDRenderText(), + paint.skPaint().isAntiAlias(), hasWCoord); + + FallbackTextHelper fallbackTextHelper(viewMatrix, paint, glyphCache, textRatio); + + sk_sp<GrTextStrike> currStrike; + + { + auto cache = blob->setupCache(runIndex, props, flags, dfPaint, nullptr); + SkPaint::GlyphCacheProc glyphCacheProc = + SkPaint::GetGlyphCacheProc(dfPaint.getTextEncoding(), true); + + const char* stop = text + byteLength; + + while (text < stop) { + const char* lastText = text; + // the last 2 parameters are ignored + const SkGlyph& glyph = glyphCacheProc(cache.get(), &text); + + if (glyph.fWidth) { + SkPoint glyphPos(offset); + glyphPos.fX += pos[0]; + glyphPos.fY += (2 == scalarsPerPosition ? pos[1] : 0); + + if (glyph.fMaskFormat == SkMask::kSDF_Format) { + DfAppendGlyph(blob, runIndex, glyphCache, &currStrike, glyph, glyphPos.fX, + glyphPos.fY, paint.filteredPremulColor(), cache.get(), textRatio); + } else { + // can't append non-SDF glyph to SDF batch, send to fallback + fallbackTextHelper.appendText(glyph, SkToInt(text - lastText), lastText, + glyphPos); + } + } + pos += scalarsPerPosition; + } + } + + fallbackTextHelper.drawText(blob, runIndex, glyphCache, props, paint, scalerContextFlags); +} + +// TODO: merge with BmpAppendGlyph +void GrTextContext::DfAppendGlyph(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* grGlyphCache, sk_sp<GrTextStrike>* strike, + const SkGlyph& skGlyph, SkScalar sx, SkScalar sy, + GrColor color, SkGlyphCache* skGlyphCache, + SkScalar textRatio) { + if (!*strike) { + *strike = grGlyphCache->getStrike(skGlyphCache); + } + + GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(), + skGlyph.getSubXFixed(), + skGlyph.getSubYFixed(), + GrGlyph::kDistance_MaskStyle); + GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, skGlyphCache); + if (!glyph) { + return; + } + + SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset); + SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset); + SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceFieldInset); + SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFieldInset); + + dx *= textRatio; + dy *= textRatio; + width *= textRatio; + height *= textRatio; + SkRect glyphRect = SkRect::MakeXYWH(sx + dx, sy + dy, width, height); + + blob->appendGlyph(runIndex, glyphRect, color, *strike, glyph, skGlyphCache, skGlyph, sx, sy, + textRatio, false); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void GrTextContext::FallbackTextHelper::appendText(const SkGlyph& glyph, int count, + const char* text, SkPoint glyphPos) { + SkScalar maxDim = SkTMax(glyph.fWidth, glyph.fHeight)*fTextRatio; + if (!fUseTransformedFallback) { + if (!fViewMatrix.isScaleTranslate() || maxDim*fMaxScale > fMaxTextSize) { + fUseTransformedFallback = true; + fMaxTextSize -= 2; // Subtract 2 to account for the bilerp pad around the glyph + } + } + + fFallbackTxt.append(count, text); + if (fUseTransformedFallback) { + // If there's a glyph in the font that's particularly large, it's possible + // that fScaledFallbackTextSize may end up minimizing too much. We'd rather skip + // that glyph than make the others blurry, so we set a minimum size of half the + // maximum text size to avoid this case. + SkScalar glyphTextSize = SkTMax(SkScalarFloorToScalar(fTextSize * fMaxTextSize/maxDim), + 0.5f*fMaxTextSize); + fTransformedFallbackTextSize = SkTMin(glyphTextSize, fTransformedFallbackTextSize); + } + *fFallbackPos.append() = glyphPos; +} + +void GrTextContext::FallbackTextHelper::drawText(GrAtlasTextBlob* blob, int runIndex, + GrGlyphCache* glyphCache, + const SkSurfaceProps& props, + const GrTextUtils::Paint& paint, + SkScalerContextFlags scalerContextFlags) { + if (fFallbackTxt.count()) { + blob->initOverride(runIndex); + blob->setHasBitmap(); + blob->setSubRunHasW(runIndex, fViewMatrix.hasPerspective()); + SkExclusiveStrikePtr cache; + const SkPaint& skPaint = paint.skPaint(); + SkPaint::GlyphCacheProc glyphCacheProc = + SkPaint::GetGlyphCacheProc(skPaint.getTextEncoding(), true); + SkColor textColor = paint.filteredPremulColor(); + SkScalar textRatio = SK_Scalar1; + if (fUseTransformedFallback) { + // Set up paint and matrix to scale glyphs + SkPaint scaledPaint(skPaint); + scaledPaint.setTextSize(fTransformedFallbackTextSize); + textRatio = fTextSize / fTransformedFallbackTextSize; + cache = blob->setupCache(runIndex, props, scalerContextFlags, scaledPaint, + &SkMatrix::I()); + } else { + cache = blob->setupCache(runIndex, props, scalerContextFlags, paint, + &fViewMatrix); + } + + sk_sp<GrTextStrike> currStrike; + const char* text = fFallbackTxt.begin(); + const char* stop = text + fFallbackTxt.count(); + SkPoint* glyphPos = fFallbackPos.begin(); + while (text < stop) { + const SkGlyph& glyph = glyphCacheProc(cache.get(), &text); + if (!fUseTransformedFallback) { + fViewMatrix.mapPoints(glyphPos, 1); + glyphPos->fX = SkScalarFloorToScalar(glyphPos->fX); + glyphPos->fY = SkScalarFloorToScalar(glyphPos->fY); + } + GrTextContext::BmpAppendGlyph(blob, runIndex, glyphCache, &currStrike, glyph, + glyphPos->fX, glyphPos->fY, textColor, + cache.get(), textRatio, fUseTransformedFallback); + glyphPos++; + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +#include "GrRenderTargetContext.h" + +std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrContext* context, + GrTextContext* textContext, + GrRenderTargetContext* rtc, + const SkPaint& skPaint, + const SkMatrix& viewMatrix, + const char* text, int x, int y) { + auto glyphCache = context->contextPriv().getGlyphCache(); + + static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType); + + size_t textLen = (int)strlen(text); + + GrTextUtils::Paint utilsPaint(&skPaint, &rtc->colorSpaceInfo()); + + // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to + // test the text op with this unit test, that is okay. + sk_sp<GrAtlasTextBlob> blob(textContext->makeDrawTextBlob( + context->contextPriv().getTextBlobCache(), glyphCache, + *context->contextPriv().caps()->shaderCaps(), utilsPaint, + GrTextContext::kTextBlobOpScalerContextFlags, viewMatrix, surfaceProps, text, + static_cast<size_t>(textLen), SkIntToScalar(x), SkIntToScalar(y))); + + return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, utilsPaint, surfaceProps, + textContext->dfAdjustTable(), rtc->textTarget()); +} + +GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) { + static uint32_t gContextID = SK_InvalidGenID; + static std::unique_ptr<GrTextContext> gTextContext; + static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType); + + if (context->uniqueID() != gContextID) { + gContextID = context->uniqueID(); + gTextContext = GrTextContext::Make(GrTextContext::Options()); + } + + // Setup dummy SkPaint / GrPaint / GrRenderTargetContext + sk_sp<GrRenderTargetContext> rtc(context->contextPriv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, 1024, 1024, kRGBA_8888_GrPixelConfig, nullptr)); + + SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); + + // Because we the GrTextUtils::Paint requires an SkPaint for font info, we ignore the GrPaint + // param. + SkPaint skPaint; + skPaint.setColor(random->nextU()); + skPaint.setLCDRenderText(random->nextBool()); + skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool()); + skPaint.setSubpixelText(random->nextBool()); + + const char* text = "The quick brown fox jumps over the lazy dog."; + + // create some random x/y offsets, including negative offsets + static const int kMaxTrans = 1024; + int xPos = (random->nextU() % 2) * 2 - 1; + int yPos = (random->nextU() % 2) * 2 - 1; + int xInt = (random->nextU() % kMaxTrans) * xPos; + int yInt = (random->nextU() % kMaxTrans) * yPos; + + return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(), + skPaint, viewMatrix, text, xInt, yInt); +} + +#endif |