blob: 50534ff17997ce4e0ceb329357892cbcb0a6874d [file]
/*
* 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 "include/cpu/Recorder.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 "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().fontCache());
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().fontCache());
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().fontCache());
REPORTER_ASSERT(reporter, context->fontCacheCountUsed() == 1);
sk_sp<SkStrike> strike32 = spec32.findOrCreateStrike(context->priv().fontCache());
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().fontCache());
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().fontCache()->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().fontCache() != contextB->priv().fontCache());
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().fontCache());
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().fontCache());
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