blob: 88d577adde655703f0adc91a5a5a149841b978f4 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkGraphics.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "include/private/SkMutex.h"
#include "src/core/SkDraw.h"
#include "src/core/SkRemoteGlyphCache.h"
#include "src/core/SkRemoteGlyphCacheImpl.h"
#include "src/core/SkStrike.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkSurfacePriv.h"
#include "src/core/SkTypeface_remote.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include "tools/fonts/TestEmptyTypeface.h"
#include "src/gpu/text/GrTextContext.h"
class DiscardableManager : public SkStrikeServer::DiscardableHandleManager,
public SkStrikeClient::DiscardableHandleManager {
public:
DiscardableManager() { sk_bzero(&fCacheMissCount, sizeof(fCacheMissCount)); }
~DiscardableManager() override = default;
// Server implementation.
SkDiscardableHandleId createHandle() override {
SkAutoMutexAcquire l(&fMutex);
// Handles starts as locked.
fLockedHandles.add(++fNextHandleId);
return fNextHandleId;
}
bool lockHandle(SkDiscardableHandleId id) override {
SkAutoMutexAcquire l(&fMutex);
if (id <= fLastDeletedHandleId) return false;
fLockedHandles.add(id);
return true;
}
// Client implementation.
bool deleteHandle(SkDiscardableHandleId id) override {
SkAutoMutexAcquire l(&fMutex);
return id <= fLastDeletedHandleId;
}
void notifyCacheMiss(SkStrikeClient::CacheMissType type) override {
SkAutoMutexAcquire l(&fMutex);
fCacheMissCount[type]++;
}
bool isHandleDeleted(SkDiscardableHandleId id) override {
SkAutoMutexAcquire l(&fMutex);
return id <= fLastDeletedHandleId;
}
void unlockAll() {
SkAutoMutexAcquire l(&fMutex);
fLockedHandles.reset();
}
void unlockAndDeleteAll() {
SkAutoMutexAcquire l(&fMutex);
fLockedHandles.reset();
fLastDeletedHandleId = fNextHandleId;
}
const SkTHashSet<SkDiscardableHandleId>& lockedHandles() const {
SkAutoMutexAcquire l(&fMutex);
return fLockedHandles;
}
SkDiscardableHandleId handleCount() {
SkAutoMutexAcquire l(&fMutex);
return fNextHandleId;
}
int cacheMissCount(uint32_t type) {
SkAutoMutexAcquire l(&fMutex);
return fCacheMissCount[type];
}
bool hasCacheMiss() const {
SkAutoMutexAcquire l(&fMutex);
for (uint32_t i = 0; i <= SkStrikeClient::CacheMissType::kLast; ++i) {
if (fCacheMissCount[i] > 0) return true;
}
return false;
}
private:
// The tests below run in parallel on multiple threads and use the same
// process global SkStrikeCache. So the implementation needs to be
// thread-safe.
mutable SkMutex fMutex;
SkDiscardableHandleId fNextHandleId = 0u;
SkDiscardableHandleId fLastDeletedHandleId = 0u;
SkTHashSet<SkDiscardableHandleId> fLockedHandles;
int fCacheMissCount[SkStrikeClient::CacheMissType::kLast + 1u];
};
sk_sp<SkTextBlob> buildTextBlob(sk_sp<SkTypeface> tf, int glyphCount) {
SkFont font;
font.setTypeface(tf);
font.setHinting(kNormal_SkFontHinting);
font.setSize(1u);
font.setEdging(SkFont::Edging::kAntiAlias);
font.setSubpixel(true);
SkTextBlobBuilder builder;
SkRect bounds = SkRect::MakeWH(10, 10);
const auto& runBuffer = builder.allocRunPosH(font, glyphCount, 0, &bounds);
SkASSERT(runBuffer.utf8text == nullptr);
SkASSERT(runBuffer.clusters == nullptr);
for (int i = 0; i < glyphCount; i++) {
runBuffer.glyphs[i] = static_cast<SkGlyphID>(i);
runBuffer.pos[i] = SkIntToScalar(i);
}
return builder.make();
}
static void compare_blobs(const SkBitmap& expected, const SkBitmap& actual,
skiatest::Reporter* reporter, int tolerance = 0) {
SkASSERT(expected.width() == actual.width());
SkASSERT(expected.height() == actual.height());
for (int i = 0; i < expected.width(); ++i) {
for (int j = 0; j < expected.height(); ++j) {
SkColor expectedColor = expected.getColor(i, j);
SkColor actualColor = actual.getColor(i, j);
if (0 == tolerance) {
REPORTER_ASSERT(reporter, expectedColor == actualColor);
} else {
for (int k = 0; k < 4; ++k) {
int expectedChannel = (expectedColor >> (k*8)) & 0xff;
int actualChannel = (actualColor >> (k*8)) & 0xff;
REPORTER_ASSERT(reporter, abs(expectedChannel - actualChannel) <= tolerance);
}
}
}
}
}
SkTextBlobCacheDiffCanvas::Settings MakeSettings(GrContext* context) {
SkTextBlobCacheDiffCanvas::Settings settings;
settings.fContextSupportsDistanceFieldText = context->supportsDistanceFieldText();
settings.fMaxTextureSize = context->maxTextureSize();
settings.fMaxTextureBytes = GrContextOptions().fGlyphCacheTextureMaximumBytes;
return settings;
}
sk_sp<SkSurface> MakeSurface(int width, int height, GrContext* context) {
const SkImageInfo info =
SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
return SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info);
}
const SkSurfaceProps FindSurfaceProps(GrContext* context) {
auto surface = MakeSurface(1, 1, context);
return surface->props();
}
SkBitmap RasterBlob(sk_sp<SkTextBlob> blob, int width, int height, const SkPaint& paint,
GrContext* context, const SkMatrix* matrix = nullptr,
SkScalar x = 0) {
auto surface = MakeSurface(width, height, context);
if (matrix) surface->getCanvas()->concat(*matrix);
surface->getCanvas()->drawTextBlob(blob.get(), x, height/2, paint);
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
surface->readPixels(bitmap, 0, 0);
return bitmap;
}
DEF_TEST(SkRemoteGlyphCache_TypefaceSerialization, reporter) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
auto server_tf = SkTypeface::MakeDefault();
auto tf_data = server.serializeTypeface(server_tf.get());
auto client_tf = client.deserializeTypeface(tf_data->data(), tf_data->size());
REPORTER_ASSERT(reporter, client_tf);
REPORTER_ASSERT(reporter, static_cast<SkTypefaceProxy*>(client_tf.get())->remoteTypefaceID() ==
server_tf->uniqueID());
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_StrikeSerialization, reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
const SkPaint paint;
// Server.
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto serverTfData = server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
auto props = FindSurfaceProps(ctxInfo.grContext());
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto clientBlob = buildTextBlob(clientTf, glyphCount);
SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint, ctxInfo.grContext());
SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, ctxInfo.grContext());
compare_blobs(expected, actual, reporter);
REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_ReleaseTypeFace, reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
// Server.
auto serverTf = TestEmptyTypeface::Make();
auto serverTfData = server.serializeTypeface(serverTf.get());
REPORTER_ASSERT(reporter, serverTf->unique());
{
const SkPaint paint;
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
REPORTER_ASSERT(reporter, !serverTf->unique());
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
}
REPORTER_ASSERT(reporter, serverTf->unique());
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_StrikeLockingServer, reporter) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
SkPaint paint;
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
// The strike from the blob should be locked after it has been drawn on the canvas.
REPORTER_ASSERT(reporter, discardableManager->handleCount() == 1u);
REPORTER_ASSERT(reporter, discardableManager->lockedHandles().count() == 1u);
// Write the strike data and unlock everything. Re-analyzing the blob should lock the handle
// again.
std::vector<uint8_t> fontData;
server.writeStrikeData(&fontData);
discardableManager->unlockAll();
REPORTER_ASSERT(reporter, discardableManager->lockedHandles().count() == 0u);
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
REPORTER_ASSERT(reporter, discardableManager->handleCount() == 1u);
REPORTER_ASSERT(reporter, discardableManager->lockedHandles().count() == 1u);
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_StrikeDeletionServer, reporter) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
SkPaint paint;
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
REPORTER_ASSERT(reporter, discardableManager->handleCount() == 1u);
// Write the strike data and delete all the handles. Re-analyzing the blob should create new
// handles.
std::vector<uint8_t> fontData;
server.writeStrikeData(&fontData);
discardableManager->unlockAndDeleteAll();
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
REPORTER_ASSERT(reporter, discardableManager->handleCount() == 2u);
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_StrikePinningClient, reporter) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
// Server.
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto serverTfData = server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
SkPaint paint;
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto* clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size()).get();
// The cache remains alive until it is pinned in the discardable manager.
SkGraphics::PurgeFontCache();
REPORTER_ASSERT(reporter, !clientTf->unique());
// Once the strike is unpinned and purged, SkStrikeClient should be the only owner of the
// clientTf.
discardableManager->unlockAndDeleteAll();
SkGraphics::PurgeFontCache();
REPORTER_ASSERT(reporter, clientTf->unique());
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_ClientMemoryAccounting, reporter) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
// Server.
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto serverTfData = server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
SkPaint paint;
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
SkStrikeCache::ValidateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_PurgesServerEntries, reporter) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
server.setMaxEntriesInDescriptorMapForTesting(1u);
SkStrikeClient client(discardableManager, false);
{
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
SkPaint paint;
REPORTER_ASSERT(reporter, server.remoteGlyphStateMapSizeForTesting() == 0u);
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
REPORTER_ASSERT(reporter, server.remoteGlyphStateMapSizeForTesting() == 1u);
}
// Serialize to release the lock from the strike server and delete all current
// handles.
std::vector<uint8_t> fontData;
server.writeStrikeData(&fontData);
discardableManager->unlockAndDeleteAll();
// Use a different typeface. Creating a new strike should evict the previous
// one.
{
auto serverTf = SkTypeface::MakeFromName("Georgia", SkFontStyle());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
SkPaint paint;
REPORTER_ASSERT(reporter, server.remoteGlyphStateMapSizeForTesting() == 1u);
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
REPORTER_ASSERT(reporter, server.remoteGlyphStateMapSizeForTesting() == 1u);
}
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsPath, reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(0);
REPORTER_ASSERT(reporter, SkDraw::ShouldDrawTextAsPaths(SkFont(), paint, SkMatrix::I()));
// Server.
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto serverTfData = server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
auto props = FindSurfaceProps(ctxInfo.grContext());
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto clientBlob = buildTextBlob(clientTf, glyphCount);
SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint, ctxInfo.grContext());
SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, ctxInfo.grContext());
compare_blobs(expected, actual, reporter, 1);
REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
SkStrikeCache::ValidateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
sk_sp<SkTextBlob> make_blob_causing_fallback(
sk_sp<SkTypeface> targetTf, const SkTypeface* glyphTf, skiatest::Reporter* reporter) {
SkFont font;
font.setSubpixel(true);
font.setSize(96);
font.setHinting(kNormal_SkFontHinting);
font.setTypeface(targetTf);
REPORTER_ASSERT(reporter, !SkDraw::ShouldDrawTextAsPaths(font, SkPaint(), SkMatrix::I()));
char s[] = "Skia";
int runSize = strlen(s);
SkTextBlobBuilder builder;
SkRect bounds = SkRect::MakeIWH(100, 100);
const auto& runBuffer = builder.allocRunPosH(font, runSize, 10, &bounds);
SkASSERT(runBuffer.utf8text == nullptr);
SkASSERT(runBuffer.clusters == nullptr);
SkFont(sk_ref_sp(glyphTf)).textToGlyphs(s, strlen(s), SkTextEncoding::kUTF8,
runBuffer.glyphs, runSize);
SkRect glyphBounds;
font.getWidths(runBuffer.glyphs, 1, nullptr, &glyphBounds);
REPORTER_ASSERT(reporter, glyphBounds.width() > SkStrikeCommon::kSkSideTooBigForAtlas);
for (int i = 0; i < runSize; i++) {
runBuffer.pos[i] = i * 10;
}
return builder.make();
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsMaskWithPathFallback,
reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
SkPaint paint;
auto serverTf = MakeResourceAsTypeface("fonts/HangingS.ttf");
// TODO: when the cq bots can handle this font remove the check.
if (serverTf == nullptr) {
return;
}
auto serverTfData = server.serializeTypeface(serverTf.get());
auto serverBlob = make_blob_causing_fallback(serverTf, serverTf.get(), reporter);
auto props = FindSurfaceProps(ctxInfo.grContext());
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto clientBlob = make_blob_causing_fallback(clientTf, serverTf.get(), reporter);
SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint, ctxInfo.grContext());
SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, ctxInfo.grContext());
compare_blobs(expected, actual, reporter);
REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
SkStrikeCache::ValidateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
#if 0
// TODO: turn this one when I figure out how to deal with the pixel variance from linear
// interpolation from GPU to GPU.
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsSDFTWithAllARGBFallback,
reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
SkPaint paint;
auto serverTf = ToolUtils::planet_typeface();
// TODO: when the cq bots can handle this font remove the check.
if (serverTf == nullptr) {
return;
}
auto serverTfData = server.serializeTypeface(serverTf.get());
auto makeBlob = [&reporter](sk_sp<SkTypeface> typeface) {
SkFont font;
font.setSubpixel(true);
font.setSize(96);
font.setHinting(kNormal_SkFontHinting);
font.setTypeface(typeface);
REPORTER_ASSERT(reporter, !SkDraw::ShouldDrawTextAsPaths(font, SkPaint(), SkMatrix::I()));
// Mercury to Uranus.
SkGlyphID glyphs[] = {1, 2, 3, 4, 5, 6, 7, 8};
SkTextBlobBuilder builder;
SkRect bounds = SkRect::MakeIWH(100, 100);
const auto& runBuffer = builder.allocRunPosH(font, SK_ARRAY_COUNT(glyphs), 100, &bounds);
SkASSERT(runBuffer.utf8text == nullptr);
SkASSERT(runBuffer.clusters == nullptr);
std::copy(std::begin(glyphs), std::end(glyphs), runBuffer.glyphs);
for (size_t i = 0; i < SK_ARRAY_COUNT(glyphs); i++) {
runBuffer.pos[i] = i * 100;
}
return builder.make();
};
auto serverBlob = makeBlob(serverTf);
auto props = FindSurfaceProps(ctxInfo.grContext());
SkTextBlobCacheDiffCanvas cache_diff_canvas(800, 800, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 400, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto clientBlob = makeBlob(clientTf);
SkBitmap expected = RasterBlob(serverBlob, 800, 800, paint, ctxInfo.grContext());
SkBitmap actual = RasterBlob(clientBlob, 800, 800, paint, ctxInfo.grContext());
// Pixel variance can be high because of the atlas placement, and large scaling in the linear
// interpolation.
compare_blobs(expected, actual, reporter, 36);
REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
SkStrikeCache::ValidateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
#endif
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextXY, reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
SkPaint paint;
paint.setAntiAlias(true);
// Server.
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto serverTfData = server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
auto props = FindSurfaceProps(ctxInfo.grContext());
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0.5, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto clientBlob = buildTextBlob(clientTf, glyphCount);
SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint, ctxInfo.grContext(), nullptr, 0.5);
SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, ctxInfo.grContext(), nullptr, 0.5);
compare_blobs(expected, actual, reporter);
REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
SkStrikeCache::ValidateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsDFT, reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
SkPaint paint;
SkFont font;
// A perspective transform forces fallback to dft.
SkMatrix matrix = SkMatrix::I();
matrix[SkMatrix::kMPersp0] = 0.5f;
REPORTER_ASSERT(reporter, matrix.hasPerspective());
SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
GrTextContext::Options options;
GrTextContext::SanitizeOptions(&options);
REPORTER_ASSERT(reporter, GrTextContext::CanDrawAsDistanceFields(
paint, font, matrix, surfaceProps, true, options));
// Server.
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto serverTfData = server.serializeTypeface(serverTf.get());
int glyphCount = 10;
auto serverBlob = buildTextBlob(serverTf, glyphCount);
const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
MakeSettings(ctxInfo.grContext()));
cache_diff_canvas.concat(matrix);
cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
// Client.
auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
REPORTER_ASSERT(reporter,
client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
auto clientBlob = buildTextBlob(clientTf, glyphCount);
SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint, ctxInfo.grContext(), &matrix);
SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, ctxInfo.grContext(), &matrix);
compare_blobs(expected, actual, reporter);
REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
SkStrikeCache::ValidateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_CacheMissReporting, reporter, ctxInfo) {
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto tfData = server.serializeTypeface(serverTf.get());
auto clientTf = client.deserializeTypeface(tfData->data(), tfData->size());
REPORTER_ASSERT(reporter, clientTf);
int glyphCount = 10;
auto clientBlob = buildTextBlob(clientTf, glyphCount);
// Raster the client-side blob without the glyph data, we should get cache miss notifications.
SkPaint paint;
SkMatrix matrix = SkMatrix::I();
RasterBlob(clientBlob, 10, 10, paint, ctxInfo.grContext(), &matrix);
REPORTER_ASSERT(reporter,
discardableManager->cacheMissCount(SkStrikeClient::kFontMetrics) == 1);
REPORTER_ASSERT(reporter,
discardableManager->cacheMissCount(SkStrikeClient::kGlyphMetrics) == 10);
// There shouldn't be any image or path requests, since we mark the glyph as empty on a cache
// miss.
REPORTER_ASSERT(reporter, discardableManager->cacheMissCount(SkStrikeClient::kGlyphImage) == 0);
REPORTER_ASSERT(reporter, discardableManager->cacheMissCount(SkStrikeClient::kGlyphPath) == 0);
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_SearchOfDesperation, reporter) {
// Build proxy typeface on the client for initializing the cache.
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto tfData = server.serializeTypeface(serverTf.get());
auto clientTf = client.deserializeTypeface(tfData->data(), tfData->size());
REPORTER_ASSERT(reporter, clientTf);
SkFont font;
font.setTypeface(clientTf);
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(SK_ColorRED);
auto lostGlyphID = SkPackedGlyphID(1, SK_FixedHalf, SK_FixedHalf);
const uint8_t glyphImage[] = {0xFF, 0xFF};
SkStrikeCache strikeCache;
// Build a fallback cache.
{
SkAutoDescriptor ad;
SkScalerContextRec rec;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
SkScalerContext::MakeRecAndEffects(
font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
SkMatrix::I(), &rec, &effects);
auto desc = SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, effects, &ad);
auto fallbackCache = strikeCache.findOrCreateStrikeExclusive(*desc, effects, *clientTf);
auto glyph = fallbackCache->getRawGlyphByID(lostGlyphID);
glyph->fMaskFormat = SkMask::kA8_Format;
glyph->fHeight = 1;
glyph->fWidth = 2;
fallbackCache->initializeImage(glyphImage, glyph->computeImageSize(), glyph);
glyph->fImage = (void *)glyphImage;
}
// Make sure we can find the fall back cache.
{
SkAutoDescriptor ad;
SkScalerContextRec rec;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
SkScalerContext::MakeRecAndEffects(
font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
SkMatrix::I(), &rec, &effects);
auto desc = SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, effects, &ad);
auto testCache = strikeCache.findStrikeExclusive(*desc);
REPORTER_ASSERT(reporter, !(testCache == nullptr));
}
// Create the target cache.
SkExclusiveStrikePtr testCache;
SkAutoDescriptor ad;
SkScalerContextRec rec;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kNone;
SkScalerContext::MakeRecAndEffects(
font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
SkMatrix::I(), &rec, &effects);
auto desc = SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, effects, &ad);
testCache = strikeCache.findStrikeExclusive(*desc);
REPORTER_ASSERT(reporter, testCache == nullptr);
testCache = strikeCache.createStrikeExclusive(*desc,
clientTf->createScalerContext(effects, desc));
auto scalerProxy = static_cast<SkScalerContextProxy*>(testCache->getScalerContext());
scalerProxy->initCache(testCache.get(), &strikeCache);
// Look for the lost glyph.
{
const auto& lostGlyph = testCache->getGlyphIDMetrics(
lostGlyphID.code(), lostGlyphID.getSubXFixed(), lostGlyphID.getSubYFixed());
testCache->findImage(lostGlyph);
REPORTER_ASSERT(reporter, lostGlyph.fHeight == 1);
REPORTER_ASSERT(reporter, lostGlyph.fWidth == 2);
REPORTER_ASSERT(reporter, lostGlyph.fMaskFormat == SkMask::kA8_Format);
REPORTER_ASSERT(reporter, memcmp(lostGlyph.fImage, glyphImage, sizeof(glyphImage)) == 0);
}
// Look for the lost glyph with a different sub-pix position.
{
const auto& lostGlyph =
testCache->getGlyphIDMetrics(lostGlyphID.code(), SK_FixedQuarter, SK_FixedQuarter);
testCache->findImage(lostGlyph);
REPORTER_ASSERT(reporter, lostGlyph.fHeight == 1);
REPORTER_ASSERT(reporter, lostGlyph.fWidth == 2);
REPORTER_ASSERT(reporter, lostGlyph.fMaskFormat == SkMask::kA8_Format);
REPORTER_ASSERT(reporter, memcmp(lostGlyph.fImage, glyphImage, sizeof(glyphImage)) == 0);
}
for (uint32_t i = 0; i <= SkStrikeClient::CacheMissType::kLast; ++i) {
if (i == SkStrikeClient::CacheMissType::kGlyphMetricsFallback ||
i == SkStrikeClient::CacheMissType::kFontMetrics) {
REPORTER_ASSERT(reporter, discardableManager->cacheMissCount(i) == 2);
} else {
REPORTER_ASSERT(reporter, discardableManager->cacheMissCount(i) == 0);
}
}
strikeCache.validateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}
DEF_TEST(SkRemoteGlyphCache_ReWriteGlyph, reporter) {
// Build proxy typeface on the client for initializing the cache.
sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
SkStrikeServer server(discardableManager.get());
SkStrikeClient client(discardableManager, false);
auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
auto tfData = server.serializeTypeface(serverTf.get());
auto clientTf = client.deserializeTypeface(tfData->data(), tfData->size());
REPORTER_ASSERT(reporter, clientTf);
SkFont font;
font.setEdging(SkFont::Edging::kAntiAlias);
SkPaint paint;
paint.setColor(SK_ColorRED);
auto lostGlyphID = SkPackedGlyphID(1, SK_FixedHalf, SK_FixedHalf);
const uint8_t glyphImage[] = {0xFF, 0xFF};
uint32_t realMask;
uint32_t fakeMask;
SkStrikeCache strikeCache;
{
SkAutoDescriptor ad;
SkScalerContextRec rec;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
font.setTypeface(serverTf);
SkScalerContext::MakeRecAndEffects(
font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
SkMatrix::I(), &rec, &effects);
auto desc = SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, effects, &ad);
auto context = serverTf->createScalerContext(effects, desc, false);
SkGlyph glyph{lostGlyphID};
context->getMetrics(&glyph);
realMask = glyph.fMaskFormat;
REPORTER_ASSERT(reporter, realMask != MASK_FORMAT_UNKNOWN);
}
// Build a fallback cache.
{
SkAutoDescriptor ad;
SkScalerContextRec rec;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
font.setTypeface(clientTf);
SkScalerContext::MakeRecAndEffects(
font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
SkMatrix::I(), &rec, &effects);
auto desc = SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, effects, &ad);
auto fallbackCache = strikeCache.findOrCreateStrikeExclusive(*desc, effects, *clientTf);
auto glyph = fallbackCache->getRawGlyphByID(lostGlyphID);
fakeMask = (realMask == SkMask::kA8_Format) ? SkMask::kBW_Format : SkMask::kA8_Format;
glyph->fMaskFormat = fakeMask;
glyph->fHeight = 1;
glyph->fWidth = 2;
fallbackCache->initializeImage(glyphImage, glyph->computeImageSize(), glyph);
}
// Send over the real glyph and make sure the client cache stays intact.
{
SkAutoDescriptor ad;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
font.setTypeface(serverTf);
auto* cacheState = server.getOrCreateCache(
paint, font, SkSurfacePropsCopyOrDefault(nullptr),
SkMatrix::I(), flags, &effects);
cacheState->addGlyph(lostGlyphID, false);
std::vector<uint8_t> serverStrikeData;
server.writeStrikeData(&serverStrikeData);
REPORTER_ASSERT(reporter,
client.readStrikeData(
serverStrikeData.data(),
serverStrikeData.size()));
}
{
SkAutoDescriptor ad;
SkScalerContextRec rec;
SkScalerContextEffects effects;
SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
font.setTypeface(clientTf);
SkScalerContext::MakeRecAndEffects(
font, paint, SkSurfaceProps(0, kUnknown_SkPixelGeometry), flags,
SkMatrix::I(), &rec, &effects);
auto desc = SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, effects, &ad);
auto fallbackCache = strikeCache.findStrikeExclusive(*desc);
REPORTER_ASSERT(reporter, fallbackCache.get() != nullptr);
auto glyph = fallbackCache->getRawGlyphByID(lostGlyphID);
REPORTER_ASSERT(reporter, glyph->fMaskFormat == fakeMask);
REPORTER_ASSERT(reporter,
memcmp(glyph->fImage, glyphImage, glyph->computeImageSize()) == 0);
}
strikeCache.validateGlyphCacheDataSize();
// Must unlock everything on termination, otherwise valgrind complains about memory leaks.
discardableManager->unlockAndDeleteAll();
}