/* * 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 "src/text/gpu/TextBlob.h" #include "include/core/SkMatrix.h" #include "include/core/SkPoint.h" #include "include/core/SkRect.h" #include "include/core/SkScalar.h" #include "include/private/SkColorData.h" #include "include/private/base/SkAssert.h" #include "include/private/base/SkCPUTypes.h" #include "src/core/SkDevice.h" #include "src/core/SkFontPriv.h" #include "src/core/SkMaskFilterBase.h" #include "src/core/SkPaintPriv.h" #include "src/core/SkScalerContext.h" #include "src/text/GlyphRun.h" #include "src/text/gpu/SlugImpl.h" #include "src/text/gpu/SubRunAllocator.h" #include "src/text/gpu/SubRunContainer.h" #include "src/text/gpu/SubRunControl.h" #include #include class SkMaskFilter; using namespace sktext::gpu; namespace { // Check for integer translate with the same 2x2 matrix. // Returns the translation, and true if the change from initial matrix to the position matrix // support using direct glyph masks. std::tuple can_use_direct( const SkMatrix& initialPositionMatrix, const SkMatrix& positionMatrix) { // The existing direct glyph info can be used if the initialPositionMatrix, and the // positionMatrix have the same 2x2, and the translation between them is integer. // Calculate the translation in source space to a translation in device space by mapping // (0, 0) through both the initial position matrix and the position matrix; take the difference. SkVector translation = positionMatrix.mapOrigin() - initialPositionMatrix.mapOrigin(); return {initialPositionMatrix.getScaleX() == positionMatrix.getScaleX() && initialPositionMatrix.getScaleY() == positionMatrix.getScaleY() && initialPositionMatrix.getSkewX() == positionMatrix.getSkewX() && initialPositionMatrix.getSkewY() == positionMatrix.getSkewY() && SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()), translation}; } static SkColor compute_canonical_color(const SkPaint& paint, bool lcd) { SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint); if (lcd) { // This is the correct computation for canonicalColor, but there are tons of cases where LCD // can be modified. For now we just regenerate if any run in a textblob has LCD. // TODO figure out where all of these modifications are and see if we can incorporate that // logic at a higher level *OR* use sRGB //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor); // TODO we want to figure out a way to be able to use the canonical color on LCD text, // see the note above. We pick a placeholder value for LCD text to ensure we always match // the same key return SK_ColorTRANSPARENT; } 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; } } // namespace namespace sktext::gpu { // -- TextBlob::Key ------------------------------------------------------------------------------ auto TextBlob::Key::Make(const GlyphRunList& glyphRunList, const SkPaint& paint, const SkMatrix& drawMatrix, const SkStrikeDeviceInfo& strikeDevice) -> std::tuple { SkASSERT(strikeDevice.fSubRunControl != nullptr); SkMaskFilterBase::BlurRec blurRec; // 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* maskFilter = paint.getMaskFilter(); bool canCache = glyphRunList.canCache() && !(paint.getPathEffect() || (maskFilter && !as_MFB(maskFilter)->asABlur(&blurRec))); TextBlob::Key key; if (canCache) { bool hasLCD = glyphRunList.anyRunsLCD(); // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry SkPixelGeometry pixelGeometry = hasLCD ? strikeDevice.fSurfaceProps.pixelGeometry() : kUnknown_SkPixelGeometry; SkColor canonicalColor = compute_canonical_color(paint, hasLCD); key.fPixelGeometry = pixelGeometry; key.fUniqueID = glyphRunList.uniqueID(); key.fStyle = paint.getStyle(); if (key.fStyle != SkPaint::kFill_Style) { key.fFrameWidth = paint.getStrokeWidth(); key.fMiterLimit = paint.getStrokeMiter(); key.fJoin = paint.getStrokeJoin(); } key.fHasBlur = maskFilter != nullptr; if (key.fHasBlur) { key.fBlurRec = blurRec; } key.fCanonicalColor = canonicalColor; key.fScalerContextFlags = SkTo(strikeDevice.fScalerContextFlags); // Do any runs use direct drawing types?. key.fHasSomeDirectSubRuns = false; SkPoint glyphRunListLocation = glyphRunList.sourceBoundsWithOrigin().center(); for (auto& run : glyphRunList) { SkScalar approximateDeviceTextSize = SkFontPriv::ApproximateTransformedTextSize(run.font(), drawMatrix, glyphRunListLocation); key.fHasSomeDirectSubRuns |= strikeDevice.fSubRunControl->isDirect(approximateDeviceTextSize, paint, drawMatrix); } if (key.fHasSomeDirectSubRuns) { // Store the fractional offset of the position. We know that the matrix can't be // perspective at this point. SkPoint mappedOrigin = drawMatrix.mapOrigin(); key.fPositionMatrix = drawMatrix; key.fPositionMatrix.setTranslateX( mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x())); key.fPositionMatrix.setTranslateY( mappedOrigin.y() - SkScalarFloorToScalar(mappedOrigin.y())); } else { // For path and SDFT, the matrix doesn't matter. key.fPositionMatrix = SkMatrix::I(); } } return {canCache, key}; } bool TextBlob::Key::operator==(const TextBlob::Key& that) const { if (fUniqueID != that.fUniqueID) { return false; } if (fCanonicalColor != that.fCanonicalColor) { return false; } if (fStyle != that.fStyle) { return false; } if (fStyle != SkPaint::kFill_Style) { if (fFrameWidth != that.fFrameWidth || fMiterLimit != that.fMiterLimit || fJoin != that.fJoin) { return false; } } if (fPixelGeometry != that.fPixelGeometry) { return false; } if (fHasBlur != that.fHasBlur) { return false; } if (fHasBlur) { if (fBlurRec.fStyle != that.fBlurRec.fStyle || fBlurRec.fSigma != that.fBlurRec.fSigma) { return false; } } if (fScalerContextFlags != that.fScalerContextFlags) { return false; } // DirectSubRuns do not support perspective when used with a TextBlob. SDFT, Transformed, // Path, and Drawable do support perspective. if (fPositionMatrix.hasPerspective() && fHasSomeDirectSubRuns) { return false; } if (fHasSomeDirectSubRuns != that.fHasSomeDirectSubRuns) { return false; } if (fHasSomeDirectSubRuns) { auto [compatible, _] = can_use_direct(fPositionMatrix, that.fPositionMatrix); return compatible; } return true; } // -- TextBlob ----------------------------------------------------------------------------------- void TextBlob::operator delete(void* p) { ::operator delete(p); } void* TextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); } void* TextBlob::operator new(size_t, void* p) { return p; } TextBlob::~TextBlob() = default; sk_sp TextBlob::Make(const GlyphRunList& glyphRunList, const SkPaint& paint, const SkMatrix& positionMatrix, SkStrikeDeviceInfo strikeDeviceInfo, StrikeForGPUCacheInterface* strikeCache) { size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList); auto [initializer, totalMemoryAllocated, alloc] = SubRunAllocator::AllocateClassMemoryAndArena(subRunSizeHint); auto container = SubRunContainer::MakeInAlloc( glyphRunList, positionMatrix, paint, strikeDeviceInfo, strikeCache, &alloc, SubRunContainer::kAddSubRuns, "TextBlob"); SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint); sk_sp blob = sk_sp(initializer.initialize(std::move(alloc), std::move(container), totalMemoryAllocated, initialLuminance)); return blob; } void TextBlob::addKey(const Key& key) { fKey = key; } bool TextBlob::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const { // A singular matrix will create a TextBlob with no SubRuns, but unknown glyphs can also // cause empty runs. If there are no subRuns, then regenerate when the matrices don't match. if (fSubRuns->isEmpty() && fSubRuns->initialPosition() != positionMatrix) { return false; } // If we have LCD text then our canonical color will be set to transparent, in this case we have // to regenerate the blob on any color change // We use the grPaint to get any color filter effects if (fKey.fCanonicalColor == SK_ColorTRANSPARENT && fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) { return false; } return fSubRuns->canReuse(paint, positionMatrix); } const TextBlob::Key& TextBlob::key() const { return fKey; } void TextBlob::draw(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint, const AtlasDrawDelegate& atlasDelegate) { fSubRuns->draw(canvas, drawOrigin, paint, this, atlasDelegate); } TextBlob::TextBlob(SubRunAllocator&& alloc, SubRunContainerOwner subRuns, int totalMemorySize, SkColor initialLuminance) : fAlloc{std::move(alloc)} , fSubRuns{std::move(subRuns)} , fSize(totalMemorySize) , fInitialLuminance{initialLuminance} { } sk_sp MakeSlug(const SkMatrix& drawMatrix, const sktext::GlyphRunList& glyphRunList, const SkPaint& paint, SkStrikeDeviceInfo strikeDeviceInfo, sktext::StrikeForGPUCacheInterface* strikeCache) { return SlugImpl::Make(drawMatrix, glyphRunList, paint, strikeDeviceInfo, strikeCache); } } // namespace sktext::gpu