/* * Copyright 2006 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/core/SkStrike.h" #include "include/core/SkDrawable.h" #include "include/core/SkFontStyle.h" #include "include/core/SkMatrix.h" #include "include/core/SkPath.h" #include "include/core/SkString.h" #include "include/core/SkTraceMemoryDump.h" #include "include/core/SkTypeface.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkTFitsIn.h" #include "src/core/SkGlyph.h" #include "src/core/SkMask.h" #include "src/core/SkReadBuffer.h" #include "src/core/SkScalerContext.h" #include "src/core/SkStrikeCache.h" #include "src/core/SkWriteBuffer.h" #include "src/text/StrikeForGPU.h" #include #include #include #include using namespace skglyph; static SkFontMetrics use_or_generate_metrics( const SkFontMetrics* metrics, SkScalerContext* context) { SkFontMetrics answer; if (metrics) { answer = *metrics; } else { context->getFontMetrics(&answer); } return answer; } SkStrike::SkStrike(SkStrikeCache* strikeCache, const SkStrikeSpec& strikeSpec, std::unique_ptr scaler, const SkFontMetrics* metrics, std::unique_ptr pinner) : fFontMetrics{use_or_generate_metrics(metrics, scaler.get())} , fRoundingSpec{scaler->isSubpixel(), scaler->computeAxisAlignmentForHText()} , fStrikeSpec{strikeSpec} , fStrikeCache{strikeCache} , fScalerContext{std::move(scaler)} , fPinner{std::move(pinner)} { SkASSERT(fScalerContext != nullptr); } class SK_SCOPED_CAPABILITY SkStrike::Monitor { public: Monitor(SkStrike* strike) SK_ACQUIRE(strike->fStrikeLock) : fStrike{strike} { fStrike->lock(); } ~Monitor() SK_RELEASE_CAPABILITY() { fStrike->unlock(); } private: SkStrike* const fStrike; }; void SkStrike::lock() { fStrikeLock.acquire(); fMemoryIncrease = 0; } void SkStrike::unlock() { const size_t memoryIncrease = fMemoryIncrease; fStrikeLock.release(); this->updateMemoryUsage(memoryIncrease); } void SkStrike::FlattenGlyphsByType(SkWriteBuffer& buffer, SkSpan images, SkSpan paths, SkSpan drawables) { SkASSERT_RELEASE(SkTFitsIn(images.size()) && SkTFitsIn(paths.size()) && SkTFitsIn(drawables.size())); buffer.writeInt(images.size()); for (SkGlyph& glyph : images) { SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); glyph.flattenMetrics(buffer); glyph.flattenImage(buffer); } buffer.writeInt(paths.size()); for (SkGlyph& glyph : paths) { SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); glyph.flattenMetrics(buffer); glyph.flattenPath(buffer); } buffer.writeInt(drawables.size()); for (SkGlyph& glyph : drawables) { SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); glyph.flattenMetrics(buffer); glyph.flattenDrawable(buffer); } } bool SkStrike::mergeFromBuffer(SkReadBuffer& buffer) { // Read glyphs with images for the current strike. const int imagesCount = buffer.readInt(); if (imagesCount == 0 && !buffer.isValid()) { return false; } { Monitor m{this}; for (int curImage = 0; curImage < imagesCount; ++curImage) { if (!this->mergeGlyphAndImageFromBuffer(buffer)) { return false; } } } // Read glyphs with paths for the current strike. const int pathsCount = buffer.readInt(); if (pathsCount == 0 && !buffer.isValid()) { return false; } { Monitor m{this}; for (int curPath = 0; curPath < pathsCount; ++curPath) { if (!this->mergeGlyphAndPathFromBuffer(buffer)) { return false; } } } // Read glyphs with drawables for the current strike. const int drawablesCount = buffer.readInt(); if (drawablesCount == 0 && !buffer.isValid()) { return false; } { Monitor m{this}; for (int curDrawable = 0; curDrawable < drawablesCount; ++curDrawable) { if (!this->mergeGlyphAndDrawableFromBuffer(buffer)) { return false; } } } return true; } SkGlyph* SkStrike::mergeGlyphAndImage(SkPackedGlyphID toID, const SkGlyph& fromGlyph) { Monitor m{this}; // TODO(herb): remove finding the glyph when setting the metrics and image are separated SkGlyphDigest* digest = fDigestForPackedGlyphID.find(toID); if (digest != nullptr) { SkGlyph* glyph = fGlyphForIndex[digest->index()]; if (fromGlyph.setImageHasBeenCalled()) { if (glyph->setImageHasBeenCalled()) { // Should never set an image on a glyph which already has an image. SkDEBUGFAIL("Re-adding image to existing glyph. This should not happen."); } // TODO: assert that any metrics on fromGlyph are the same. fMemoryIncrease += glyph->setMetricsAndImage(&fAlloc, fromGlyph); } return glyph; } else { SkGlyph* glyph = fAlloc.make(toID); fMemoryIncrease += glyph->setMetricsAndImage(&fAlloc, fromGlyph) + sizeof(SkGlyph); (void)this->addGlyphAndDigest(glyph); return glyph; } } const SkPath* SkStrike::mergePath(SkGlyph* glyph, const SkPath* path, bool hairline, bool modified) { Monitor m{this}; if (glyph->setPathHasBeenCalled()) { SkDEBUGFAIL("Re-adding path to existing glyph. This should not happen."); } if (glyph->setPath(&fAlloc, path, hairline, modified)) { fMemoryIncrease += glyph->path()->approximateBytesUsed(); } return glyph->path(); } const SkDrawable* SkStrike::mergeDrawable(SkGlyph* glyph, sk_sp drawable) { Monitor m{this}; if (glyph->setDrawableHasBeenCalled()) { SkDEBUGFAIL("Re-adding drawable to existing glyph. This should not happen."); } if (glyph->setDrawable(&fAlloc, std::move(drawable))) { fMemoryIncrease += glyph->drawable()->approximateBytesUsed(); SkASSERT(fMemoryIncrease > 0); } return glyph->drawable(); } void SkStrike::findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos, SkGlyph* glyph, SkScalar* array, int* count) { SkAutoMutexExclusive lock{fStrikeLock}; glyph->ensureIntercepts(bounds, scale, xPos, array, count, &fAlloc); } SkSpan SkStrike::metrics( SkSpan glyphIDs, const SkGlyph* results[]) { Monitor m{this}; return this->internalPrepare(glyphIDs, kMetricsOnly, results); } SkSpan SkStrike::preparePaths( SkSpan glyphIDs, const SkGlyph* results[]) { Monitor m{this}; return this->internalPrepare(glyphIDs, kMetricsAndPath, results); } SkSpan SkStrike::prepareImages( SkSpan glyphIDs, const SkGlyph* results[]) { const SkGlyph** cursor = results; Monitor m{this}; for (auto glyphID : glyphIDs) { SkGlyph* glyph = this->glyph(glyphID); this->prepareForImage(glyph); *cursor++ = glyph; } return {results, glyphIDs.size()}; } SkSpan SkStrike::prepareDrawables( SkSpan glyphIDs, const SkGlyph* results[]) { const SkGlyph** cursor = results; { Monitor m{this}; for (auto glyphID : glyphIDs) { SkGlyph* glyph = this->glyph(SkPackedGlyphID{glyphID}); this->prepareForDrawable(glyph); *cursor++ = glyph; } } return {results, glyphIDs.size()}; } void SkStrike::glyphIDsToPaths(SkSpan idsOrPaths) { Monitor m{this}; for (sktext::IDOrPath& idOrPath : idsOrPaths) { SkGlyph* glyph = this->glyph(SkPackedGlyphID{idOrPath.fGlyphID}); this->prepareForPath(glyph); new (&idOrPath.fPath) SkPath{*glyph->path()}; } } void SkStrike::glyphIDsToDrawables(SkSpan idsOrDrawables) { Monitor m{this}; for (sktext::IDOrDrawable& idOrDrawable : idsOrDrawables) { SkGlyph* glyph = this->glyph(SkPackedGlyphID{idOrDrawable.fGlyphID}); this->prepareForDrawable(glyph); SkASSERT(glyph->drawable() != nullptr); idOrDrawable.fDrawable = glyph->drawable(); } } void SkStrike::dump() const { SkAutoMutexExclusive lock{fStrikeLock}; const SkTypeface* face = fScalerContext->getTypeface(); const SkScalerContextRec& rec = fScalerContext->getRec(); SkMatrix matrix; rec.getSingleMatrix(&matrix); matrix.preScale(SkScalarInvert(rec.fTextSize), SkScalarInvert(rec.fTextSize)); SkString name; face->getFamilyName(&name); SkString msg; SkFontStyle style = face->fontStyle(); msg.printf("cache typeface:%x %25s:(%d,%d,%d)\n %s glyphs:%3d", face->uniqueID(), name.c_str(), style.weight(), style.width(), style.slant(), rec.dump().c_str(), fDigestForPackedGlyphID.count()); SkDebugf("%s\n", msg.c_str()); } void SkStrike::dumpMemoryStatistics(SkTraceMemoryDump* dump) const { SkAutoMutexExclusive lock{fStrikeLock}; const SkTypeface* face = fScalerContext->getTypeface(); const SkScalerContextRec& rec = fScalerContext->getRec(); SkString fontName; face->getFamilyName(&fontName); // Replace all special characters with '_'. for (size_t index = 0; index < fontName.size(); ++index) { if (!std::isalnum(fontName[index])) { fontName[index] = '_'; } } SkString dumpName = SkStringPrintf("%s/%s_%u/%p", SkStrikeCache::kGlyphCacheDumpName, fontName.c_str(), rec.fTypefaceID, this); dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", fMemoryUsed); dump->dumpNumericValue(dumpName.c_str(), "glyph_count", "objects", fDigestForPackedGlyphID.count()); dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); } SkGlyph* SkStrike::glyph(SkGlyphDigest digest) { return fGlyphForIndex[digest.index()]; } SkGlyph* SkStrike::glyph(SkPackedGlyphID packedGlyphID) { SkGlyphDigest digest = this->digestFor(kDirectMask, packedGlyphID); return this->glyph(digest); } SkGlyphDigest SkStrike::digestFor(ActionType actionType, SkPackedGlyphID packedGlyphID) { SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(packedGlyphID); if (digestPtr != nullptr && digestPtr->actionFor(actionType) != GlyphAction::kUnset) { return *digestPtr; } SkGlyph* glyph; if (digestPtr != nullptr) { glyph = fGlyphForIndex[digestPtr->index()]; } else { glyph = fAlloc.make(fScalerContext->makeGlyph(packedGlyphID, &fAlloc)); fMemoryIncrease += sizeof(SkGlyph); digestPtr = this->addGlyphAndDigest(glyph); } digestPtr->setActionFor(actionType, glyph, this); return *digestPtr; } SkGlyphDigest* SkStrike::addGlyphAndDigest(SkGlyph* glyph) { size_t index = fGlyphForIndex.size(); SkGlyphDigest digest = SkGlyphDigest{index, *glyph}; SkGlyphDigest* newDigest = fDigestForPackedGlyphID.set(digest); fGlyphForIndex.push_back(glyph); return newDigest; } bool SkStrike::prepareForImage(SkGlyph* glyph) { if (glyph->setImage(&fAlloc, fScalerContext.get())) { fMemoryIncrease += glyph->imageSize(); } return glyph->image() != nullptr; } bool SkStrike::prepareForPath(SkGlyph* glyph) { if (glyph->setPath(&fAlloc, fScalerContext.get())) { fMemoryIncrease += glyph->path()->approximateBytesUsed(); } return glyph->path() !=nullptr; } bool SkStrike::prepareForDrawable(SkGlyph* glyph) { if (glyph->setDrawable(&fAlloc, fScalerContext.get())) { size_t increase = glyph->drawable()->approximateBytesUsed(); SkASSERT(increase > 0); fMemoryIncrease += increase; } return glyph->drawable() != nullptr; } SkGlyph* SkStrike::mergeGlyphFromBuffer(SkReadBuffer& buffer) { SkASSERT(buffer.isValid()); std::optional prototypeGlyph = SkGlyph::MakeFromBuffer(buffer); if (!buffer.validate(prototypeGlyph.has_value())) { return nullptr; } // Check if this glyph has already been seen. SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(prototypeGlyph->getPackedID()); if (digestPtr != nullptr) { return fGlyphForIndex[digestPtr->index()]; } // This is the first time. Allocate a new glyph. SkGlyph* glyph = fAlloc.make(prototypeGlyph.value()); fMemoryIncrease += sizeof(SkGlyph); this->addGlyphAndDigest(glyph); return glyph; } bool SkStrike::mergeGlyphAndImageFromBuffer(SkReadBuffer& buffer) { SkASSERT(buffer.isValid()); SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer); if (!buffer.validate(glyph != nullptr)) { return false; } fMemoryIncrease += glyph->addImageFromBuffer(buffer, &fAlloc); return buffer.isValid(); } bool SkStrike::mergeGlyphAndPathFromBuffer(SkReadBuffer& buffer) { SkASSERT(buffer.isValid()); SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer); if (!buffer.validate(glyph != nullptr)) { return false; } fMemoryIncrease += glyph->addPathFromBuffer(buffer, &fAlloc); return buffer.isValid(); } bool SkStrike::mergeGlyphAndDrawableFromBuffer(SkReadBuffer& buffer) { SkASSERT(buffer.isValid()); SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer); if (!buffer.validate(glyph != nullptr)) { return false; } fMemoryIncrease += glyph->addDrawableFromBuffer(buffer, &fAlloc); return buffer.isValid(); } SkSpan SkStrike::internalPrepare( SkSpan glyphIDs, PathDetail pathDetail, const SkGlyph** results) { const SkGlyph** cursor = results; for (auto glyphID : glyphIDs) { SkGlyph* glyph = this->glyph(SkPackedGlyphID{glyphID}); if (pathDetail == kMetricsAndPath) { this->prepareForPath(glyph); } *cursor++ = glyph; } return {results, glyphIDs.size()}; } void SkStrike::updateMemoryUsage(size_t increase) { if (increase > 0) { // fRemoved and the cache's total memory are managed under the cache's lock. This allows // them to be accessed under LRU operation. SkAutoMutexExclusive lock{fStrikeCache->fLock}; fMemoryUsed += increase; if (!fRemoved) { fStrikeCache->fTotalMemoryUsed += increase; } } }