| /* | |
| * Copyright 2020 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/RasterContext.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkContext.h" | |
| #include "include/core/SkContextOptions.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkFontStyle.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkSurfaceProps.h" | |
| #include "include/core/SkTextBlob.h" | |
| #include "include/core/SkTypeface.h" | |
| #include "src/core/SkContextPriv.h" | |
| #include "src/core/SkScalerContext.h" | |
| #include "src/core/SkStrike.h" // IWYU pragma: keep | |
| #include "src/core/SkStrikeCache.h" | |
| #include "src/core/SkStrikeSpec.h" | |
| #include "src/cpu/Recorder.h" | |
| #include "tests/Test.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include <cstddef> | |
| #include <memory> | |
| namespace { | |
| SkFont make_test_font(float size = 16.0f) { | |
| sk_sp<SkTypeface> typeface = | |
| ToolUtils::CreatePortableTypeface("serif", SkFontStyle::Italic()); | |
| SkFont font; | |
| font.setEdging(SkFont::Edging::kAntiAlias); | |
| font.setSubpixel(true); | |
| font.setTypeface(typeface); | |
| font.setSize(size); | |
| return font; | |
| } | |
| SkStrikeSpec make_test_strike_spec(const SkFont& font) { | |
| SkPaint defaultPaint; | |
| return SkStrikeSpec::MakeMask(font, | |
| defaultPaint, | |
| SkSurfaceProps(0, kUnknown_SkPixelGeometry), | |
| SkScalerContextFlags::kNone, | |
| SkMatrix::I()); | |
| } | |
| } // namespace | |
| DEF_TEST(SkStrikeCache_CachePurge, Reporter) { | |
| SkStrikeCache cache; | |
| SkFont font = make_test_font(); | |
| SkStrikeSpec strikeSpec = make_test_strike_spec(font); | |
| // Initially empty cache | |
| REPORTER_ASSERT(Reporter, cache.getTotalMemoryUsed() == 0); | |
| { | |
| sk_sp<SkStrike> strike = strikeSpec.findOrCreateStrike(&cache); | |
| } | |
| // Stuff in cache. | |
| REPORTER_ASSERT(Reporter, cache.getTotalMemoryUsed() > 0); | |
| cache.purgeAll(); | |
| // Purged cache. | |
| REPORTER_ASSERT(Reporter, cache.getTotalMemoryUsed() == 0); | |
| // Smallest cache. | |
| cache.setCacheSizeLimit(0); | |
| { | |
| sk_sp<SkStrike> strike = strikeSpec.findOrCreateStrike(&cache); | |
| REPORTER_ASSERT(Reporter, cache.getTotalMemoryUsed() == 0); | |
| } | |
| REPORTER_ASSERT(Reporter, cache.getTotalMemoryUsed() == 0); | |
| } | |
| // TODO(alexisdavidc): Re-enable once SkContext local strike cache support is implemented. | |
| #if 0 | |
| DEF_TEST(SkStrikeCache_ContextOptionsAndLimits, reporter) { | |
| // 1. Default options | |
| SkContextOptions defaultOptions; | |
| std::unique_ptr<SkContext> defaultCtx = SkContexts::MakeRaster(defaultOptions); | |
| REPORTER_ASSERT(reporter, defaultCtx != nullptr); | |
| REPORTER_ASSERT(reporter, defaultCtx->fontCacheLimit() == SK_DEFAULT_FONT_CACHE_LIMIT); | |
| REPORTER_ASSERT(reporter, | |
| defaultCtx->fontCacheCountLimit() == SK_DEFAULT_FONT_CACHE_COUNT_LIMIT); | |
| REPORTER_ASSERT(reporter, defaultCtx->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, defaultCtx->fontCacheCountUsed() == 0); | |
| // 2. Custom options | |
| SkContextOptions customOptions; | |
| customOptions.fFontCacheLimit = 4 * 1024 * 1024; | |
| customOptions.fFontCacheCountLimit = 512; | |
| std::unique_ptr<SkContext> customCtx = SkContexts::MakeRaster(customOptions); | |
| REPORTER_ASSERT(reporter, customCtx != nullptr); | |
| REPORTER_ASSERT(reporter, customCtx->fontCacheLimit() == customOptions.fFontCacheLimit); | |
| REPORTER_ASSERT(reporter, | |
| customCtx->fontCacheCountLimit() == customOptions.fFontCacheCountLimit); | |
| REPORTER_ASSERT(reporter, customCtx->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, customCtx->fontCacheCountUsed() == 0); | |
| // 3. Dynamic limit updates return previous value and apply new limit | |
| size_t prevByteLimit = customCtx->setFontCacheLimit(1024 * 1024); | |
| REPORTER_ASSERT(reporter, prevByteLimit == customOptions.fFontCacheLimit); | |
| REPORTER_ASSERT(reporter, customCtx->fontCacheLimit() == 1024 * 1024); | |
| int prevCountLimit = customCtx->setFontCacheCountLimit(128); | |
| REPORTER_ASSERT(reporter, prevCountLimit == customOptions.fFontCacheCountLimit); | |
| REPORTER_ASSERT(reporter, customCtx->fontCacheCountLimit() == 128); | |
| } | |
| DEF_TEST(SkStrikeCache_ContextPurgeAndEviction, reporter) { | |
| std::unique_ptr<SkContext> context = SkContexts::MakeRaster({}); | |
| REPORTER_ASSERT(reporter, context != nullptr); | |
| SkFont font16 = make_test_font(16.0f); | |
| SkFont font32 = make_test_font(32.0f); | |
| SkStrikeSpec spec16 = make_test_strike_spec(font16); | |
| SkStrikeSpec spec32 = make_test_strike_spec(font32); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 0); | |
| // Populate the context's strike cache. | |
| { | |
| sk_sp<SkStrike> strike = spec16.findOrCreateStrike(context->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, strike != nullptr); | |
| } | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() > 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1); | |
| // Re-querying the same strike spec hits the existing cached entry. | |
| size_t usedAfterFirstStrike = context->fontCacheUsed(); | |
| { | |
| sk_sp<SkStrike> strike = spec16.findOrCreateStrike(context->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, strike != nullptr); | |
| } | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() == usedAfterFirstStrike); | |
| // Purging the font cache clears entries without altering the configured limits. | |
| size_t limitBeforePurge = context->fontCacheLimit(); | |
| context->purgeFontCache(); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheLimit() == limitBeforePurge); | |
| // Setting count limit to 1 evicts the least-recently-used strike when a 2nd strike is added. | |
| context->setFontCacheCountLimit(1); | |
| { | |
| sk_sp<SkStrike> strike16 = spec16.findOrCreateStrike(context->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1); | |
| sk_sp<SkStrike> strike32 = spec32.findOrCreateStrike(context->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1); | |
| } | |
| // Setting byte limit to 0 immediately evicts cached strikes. | |
| context->setFontCacheLimit(0); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 0); | |
| { | |
| sk_sp<SkStrike> strike = spec16.findOrCreateStrike(context->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 0); | |
| } | |
| } | |
| DEF_TEST(SkStrikeCache_ContextPurgePinned, reporter) { | |
| std::unique_ptr<SkContext> context = SkContexts::MakeRaster({}); | |
| REPORTER_ASSERT(reporter, context != nullptr); | |
| struct TogglePinner : public SkStrikePinner { | |
| bool fCanDelete = false; | |
| bool canDelete() override { return fCanDelete; } | |
| }; | |
| auto pinner = std::make_unique<TogglePinner>(); | |
| TogglePinner* pinnerPtr = pinner.get(); | |
| SkFont font = make_test_font(20.0f); | |
| SkStrikeSpec strikeSpec = make_test_strike_spec(font); | |
| { | |
| sk_sp<SkStrike> strike = context->priv().strikeCache()->createStrike( | |
| strikeSpec, nullptr, std::move(pinner)); | |
| REPORTER_ASSERT(reporter, strike != nullptr); | |
| } | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() > 0); | |
| // Drop byte limit to 0 while the strike is pinned (fCanDelete == false). | |
| context->setFontCacheLimit(0); | |
| context->purgePinnedFontCache(); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() > 0); | |
| // Once the client unpins the strike, purgePinnedFontCache() evicts it. | |
| pinnerPtr->fCanDelete = true; | |
| context->purgePinnedFontCache(); | |
| REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 0); | |
| REPORTER_ASSERT(reporter, context->fontCacheUsed() == 0); | |
| } | |
| DEF_TEST(SkStrikeCache_IndependentContextsIsolation, reporter) { | |
| std::unique_ptr<SkContext> contextA = SkContexts::MakeRaster({}); | |
| std::unique_ptr<SkContext> contextB = SkContexts::MakeRaster({}); | |
| REPORTER_ASSERT(reporter, contextA != nullptr); | |
| REPORTER_ASSERT(reporter, contextB != nullptr); | |
| REPORTER_ASSERT(reporter, contextA->priv().strikeCache() != contextB->priv().strikeCache()); | |
| SkFont font = make_test_font(18.0f); | |
| SkStrikeSpec strikeSpec = make_test_strike_spec(font); | |
| // Populating contextA's strike cache does not affect contextB. | |
| { | |
| sk_sp<SkStrike> strikeA = strikeSpec.findOrCreateStrike(contextA->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, strikeA != nullptr); | |
| } | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheUsed() > 0); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheCountUsed() == 0); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheUsed() == 0); | |
| // Populate contextB's strike cache as well. | |
| { | |
| sk_sp<SkStrike> strikeB = strikeSpec.findOrCreateStrike(contextB->priv().strikeCache()); | |
| REPORTER_ASSERT(reporter, strikeB != nullptr); | |
| } | |
| REPORTER_ASSERT(reporter, contextB->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheUsed() > 0); | |
| // Purging contextA leaves contextB's cached strikes intact. | |
| contextA->purgeFontCache(); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 0); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheUsed() > 0); | |
| // Changing limits on contextA does not affect contextB. | |
| contextA->setFontCacheLimit(1024); | |
| contextA->setFontCacheCountLimit(4); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheLimit() == SK_DEFAULT_FONT_CACHE_LIMIT); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheCountLimit() == SK_DEFAULT_FONT_CACHE_COUNT_LIMIT); | |
| } | |
| DEF_TEST(SkStrikeCache_ClientDrawTextPopulatesContextCache, reporter) { | |
| std::unique_ptr<SkContext> contextA = SkContexts::MakeRaster({}); | |
| std::unique_ptr<SkContext> contextB = SkContexts::MakeRaster({}); | |
| REPORTER_ASSERT(reporter, contextA != nullptr); | |
| REPORTER_ASSERT(reporter, contextB != nullptr); | |
| std::unique_ptr<skcpu::Recorder> recorderA = contextA->makeCPURecorder(); | |
| REPORTER_ASSERT(reporter, recorderA != nullptr); | |
| SkImageInfo imageInfo = | |
| SkImageInfo::Make(200, 100, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| sk_sp<SkSurface> surfaceA = recorderA->makeBitmapSurface(imageInfo); | |
| REPORTER_ASSERT(reporter, surfaceA != nullptr); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 0); | |
| SkFont font16 = make_test_font(16.0f); | |
| SkPaint paint; | |
| // Drawing text on surfaceA populates contextA's strike cache, leaving contextB untouched. | |
| surfaceA->getCanvas()->drawString("Hello SkContext", 10.0f, 30.0f, font16, paint); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheUsed() > 0); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 1); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, contextB->fontCacheCountUsed() == 0); | |
| // Drawing another string with the same font & matrix reuses the cached strike in contextA. | |
| surfaceA->getCanvas()->drawString("More glyphs", 10.0f, 50.0f, font16, paint); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 1); | |
| // Drawing a TextBlob with a different font size adds a second strike to contextA's cache. | |
| SkFont font32 = make_test_font(32.0f); | |
| sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString("Blob Text", font32); | |
| surfaceA->getCanvas()->drawTextBlob(blob, 10.0f, 85.0f, paint); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 2); | |
| // Purging contextA's font cache resets its memory and count usage to 0. | |
| contextA->purgeFontCache(); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheUsed() == 0); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 0); | |
| // Re-drawing after purge repopulates contextA's strike cache. | |
| surfaceA->getCanvas()->drawString("After purge", 10.0f, 30.0f, font16, paint); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheUsed() > 0); | |
| REPORTER_ASSERT(reporter, contextA->fontCacheCountUsed() == 1); | |
| } | |
| #endif |