/* * 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/SkAlphaType.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkColorSpace.h" #include "include/core/SkColorType.h" #include "include/core/SkData.h" #include "include/core/SkFont.h" #include "include/core/SkFontStyle.h" #include "include/core/SkFontTypes.h" #include "include/core/SkGraphics.h" #include "include/core/SkImageInfo.h" #include "include/core/SkMatrix.h" #include "include/core/SkPaint.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkScalar.h" #include "include/core/SkSerialProcs.h" #include "include/core/SkSurface.h" #include "include/core/SkSurfaceProps.h" #include "include/core/SkTextBlob.h" #include "include/core/SkTypeface.h" #include "include/core/SkTypes.h" #include "include/gpu/GpuTypes.h" #include "include/gpu/GrContextOptions.h" #include "include/gpu/GrDirectContext.h" #include "include/gpu/GrRecordingContext.h" #include "src/core/SkTHash.h" #include "include/private/base/SkMalloc.h" #include "include/private/base/SkMutex.h" #include "include/private/chromium/SkChromeRemoteGlyphCache.h" #include "include/private/chromium/Slug.h" #include "src/core/SkFontPriv.h" #include "src/core/SkGlyph.h" #include "src/core/SkStrikeSpec.h" #include "src/core/SkTypeface_remote.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrDirectContextPriv.h" #include "src/gpu/ganesh/GrRecordingContextPriv.h" #include "src/gpu/ganesh/GrShaderCaps.h" #include "src/text/gpu/SDFTControl.h" #include "tests/CtsEnforcement.h" #include "tests/Test.h" #include "tools/Resources.h" #include "tools/ToolUtils.h" #include "tools/fonts/TestEmptyTypeface.h" #include #include #include #include #include #include // Since SkRemoteGlyphCache is not re-entrant, we can't use it while drawing slugs to simulate // text blobs in the GPU stack. #if !defined(SK_EXPERIMENTAL_SIMULATE_DRAWGLYPHRUNLIST_WITH_SLUG_STRIKE_SERIALIZE) using Slug = sktext::gpu::Slug; class DiscardableManager : public SkStrikeServer::DiscardableHandleManager, public SkStrikeClient::DiscardableHandleManager { public: DiscardableManager() { sk_bzero(&fCacheMissCount, sizeof(fCacheMissCount)); } ~DiscardableManager() override = default; // Server implementation. SkDiscardableHandleId createHandle() override { SkAutoMutexExclusive l(fMutex); // Handles starts as locked. fLockedHandles.add(++fNextHandleId); return fNextHandleId; } bool lockHandle(SkDiscardableHandleId id) override { SkAutoMutexExclusive l(fMutex); if (id <= fLastDeletedHandleId) return false; fLockedHandles.add(id); return true; } // Client implementation. bool deleteHandle(SkDiscardableHandleId id) override { SkAutoMutexExclusive l(fMutex); return id <= fLastDeletedHandleId; } void notifyCacheMiss(SkStrikeClient::CacheMissType type, int fontSize) override { SkAutoMutexExclusive l(fMutex); fCacheMissCount[type]++; } bool isHandleDeleted(SkDiscardableHandleId id) override { SkAutoMutexExclusive l(fMutex); return id <= fLastDeletedHandleId; } void unlockAll() { SkAutoMutexExclusive l(fMutex); fLockedHandles.reset(); } void unlockAndDeleteAll() { SkAutoMutexExclusive l(fMutex); fLockedHandles.reset(); fLastDeletedHandleId = fNextHandleId; } const SkTHashSet& lockedHandles() const { SkAutoMutexExclusive l(fMutex); return fLockedHandles; } SkDiscardableHandleId handleCount() { SkAutoMutexExclusive l(fMutex); return fNextHandleId; } int cacheMissCount(uint32_t type) { SkAutoMutexExclusive l(fMutex); return fCacheMissCount[type]; } bool hasCacheMiss() const { SkAutoMutexExclusive l(fMutex); for (uint32_t i = 0; i <= SkStrikeClient::CacheMissType::kLast; ++i) { if (fCacheMissCount[i] > 0) { return true; } } return false; } void resetCacheMissCounts() { SkAutoMutexExclusive l(fMutex); sk_bzero(&fCacheMissCount, sizeof(fCacheMissCount)); } 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 fLockedHandles; int fCacheMissCount[SkStrikeClient::CacheMissType::kLast + 1u]; }; sk_sp buildTextBlob(sk_sp tf, int glyphCount, int textSize = 1) { SkFont font; font.setTypeface(tf); font.setHinting(SkFontHinting::kNormal); font.setSize(textSize); 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(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); } } } } } sk_sp MakeSurface(int width, int height, GrRecordingContext* rContext) { const SkImageInfo info = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType); return SkSurface::MakeRenderTarget(rContext, skgpu::Budgeted::kNo, info); } SkSurfaceProps FindSurfaceProps(GrRecordingContext* rContext) { auto surface = MakeSurface(1, 1, rContext); return surface->props(); } SkBitmap RasterBlob(sk_sp blob, int width, int height, const SkPaint& paint, GrRecordingContext* rContext, const SkMatrix* matrix = nullptr, SkScalar x = 0) { auto surface = MakeSurface(width, height, rContext); 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; } SkBitmap RasterBlobThroughSlug(sk_sp blob, int width, int height, const SkPaint& paint, GrRecordingContext* rContext, const SkMatrix* matrix = nullptr, SkScalar x = 0) { auto surface = MakeSurface(width, height, rContext); if (matrix) { surface->getCanvas()->concat(*matrix); } auto canvas = surface->getCanvas(); auto slug = Slug::ConvertBlob(canvas, *blob, {x, height/2.0f}, paint); slug->draw(canvas); SkBitmap bitmap; bitmap.allocN32Pixels(width, height); surface->readPixels(bitmap, 0, 0); return bitmap; } SkBitmap RasterSlug(sk_sp slug, int width, int height, const SkPaint& paint, GrRecordingContext* rContext, const SkMatrix* matrix = nullptr, SkScalar x = 0) { auto surface = MakeSurface(width, height, rContext); auto canvas = surface->getCanvas(); if (matrix) { canvas->concat(*matrix); } slug->draw(canvas); SkBitmap bitmap; bitmap.allocN32Pixels(width, height); surface->readPixels(bitmap, 0, 0); return bitmap; } DEF_TEST(SkRemoteGlyphCache_TypefaceSerialization, reporter) { sk_sp discardableManager = sk_make_sp(); 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(client_tf.get())->remoteTypefaceID() == server_tf->uniqueID()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_StrikeSerialization, reporter, ctxInfo, CtsEnforcement::kNever) { auto dContext = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); 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(dContext); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, dContext->supportsDistanceFieldText(), !dContext->priv().caps()->disablePerspectiveSDFText()); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); std::vector 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, dContext); SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, dContext); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } static void use_padding_options(GrContextOptions* options) { options->fSupportBilerpFromGlyphAtlas = true; } DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_StrikeSerializationSlug, sk_gpu_test::GrContextFactory::IsRenderingContext, reporter, ctxInfo, use_padding_options, CtsEnforcement::kNever) { auto dContext = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); 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(dContext); std::unique_ptr analysisCanvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, dContext->supportsDistanceFieldText(), !dContext->priv().caps()->disablePerspectiveSDFText()); // Generate strike updates. (void)Slug::ConvertBlob(analysisCanvas.get(), *serverBlob, {0, 0}, paint); std::vector 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 = RasterBlobThroughSlug(serverBlob, 10, 10, paint, dContext); SkBitmap actual = RasterBlobThroughSlug(clientBlob, 10, 10, paint, dContext); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_StrikeSerializationSlugForcePath, sk_gpu_test::GrContextFactory::IsRenderingContext, reporter, ctxInfo, use_padding_options, CtsEnforcement::kNever) { auto dContext = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); 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, 360); auto props = FindSurfaceProps(dContext); std::unique_ptr analysisCanvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, dContext->supportsDistanceFieldText(), !dContext->priv().caps()->disablePerspectiveSDFText()); // Generate strike updates. (void)Slug::ConvertBlob(analysisCanvas.get(), *serverBlob, {0, 0}, paint); std::vector 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, 360); SkBitmap expected = RasterBlobThroughSlug(serverBlob, 10, 10, paint, dContext); SkBitmap actual = RasterBlobThroughSlug(clientBlob, 10, 10, paint, dContext); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_SlugSerialization, sk_gpu_test::GrContextFactory::IsRenderingContext, reporter, ctxInfo, use_padding_options, CtsEnforcement::kNever) { auto dContext = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); 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(dContext); std::unique_ptr analysisCanvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, dContext->supportsDistanceFieldText(), !dContext->priv().caps()->disablePerspectiveSDFText()); // Generate strike updates. auto srcSlug = Slug::ConvertBlob(analysisCanvas.get(), *serverBlob, {0.3f, 0}, paint); auto dstSlugData = srcSlug->serialize(); std::vector serverStrikeData; server.writeStrikeData(&serverStrikeData); // Client. REPORTER_ASSERT(reporter, client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); SkBitmap expected = RasterSlug(srcSlug, 10, 10, paint, dContext); auto dstSlug = client.deserializeSlug(dstSlugData->data(), dstSlugData->size()); REPORTER_ASSERT(reporter, dstSlug != nullptr); SkBitmap actual = RasterSlug(dstSlug, 10, 10, paint, dContext); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_ReleaseTypeFace, reporter, ctxInfo, CtsEnforcement::kNever) { sk_sp discardableManager = sk_make_sp(); 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, ctxInfo.directContext()->supportsDistanceFieldText(), !ctxInfo.directContext()->priv().caps()->disablePerspectiveSDFText()); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); REPORTER_ASSERT(reporter, !serverTf->unique()); std::vector 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 = sk_make_sp(); 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); 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 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 = sk_make_sp(); 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); 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 fontData; server.writeStrikeData(&fontData); // Another analysis pass, to ensure that deleting handles after a complete cache hit still // works. This is a regression test for crbug.com/999682. cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); server.writeStrikeData(&fontData); REPORTER_ASSERT(reporter, discardableManager->handleCount() == 1u); 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 = sk_make_sp(); 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); SkPaint paint; cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); std::vector 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 = sk_make_sp(); 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); SkPaint paint; cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); std::vector serverStrikeData; server.writeStrikeData(&serverStrikeData); // Client. REPORTER_ASSERT(reporter, client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_TEST(SkRemoteGlyphCache_PurgesServerEntries, reporter) { sk_sp discardableManager = sk_make_sp(); 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); SkPaint paint; REPORTER_ASSERT(reporter, server.remoteStrikeMapSizeForTesting() == 0u); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); REPORTER_ASSERT(reporter, server.remoteStrikeMapSizeForTesting() == 1u); } // Serialize to release the lock from the strike server and delete all current // handles. std::vector 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; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); SkPaint paint; REPORTER_ASSERT(reporter, server.remoteStrikeMapSizeForTesting() == 1u); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); REPORTER_ASSERT(reporter, server.remoteStrikeMapSizeForTesting() == 1u); } // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsPath, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); SkStrikeServer server(discardableManager.get()); SkStrikeClient client(discardableManager, false); SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(0); REPORTER_ASSERT(reporter, SkStrikeSpec::ShouldDrawAsPath(paint, SkFont(), 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(direct); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); std::vector 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, direct); SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, direct); compare_blobs(expected, actual, reporter, 1); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } sk_sp make_blob_causing_fallback( sk_sp targetTf, const SkTypeface* glyphTf, skiatest::Reporter* reporter) { SkFont font; font.setSubpixel(true); font.setSize(96); font.setHinting(SkFontHinting::kNormal); font.setTypeface(targetTf); REPORTER_ASSERT(reporter, !SkStrikeSpec::ShouldDrawAsPath(SkPaint(), font, 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() > SkGlyphDigest::kSkSideTooBigForAtlas); for (int i = 0; i < runSize; i++) { runBuffer.pos[i] = i * 10; } return builder.make(); } DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsMaskWithPathFallback, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); 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(direct); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); std::vector 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, direct); SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, direct); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsSDFTWithAllARGBFallback, reporter, ctxInfo, CtsEnforcement::kNever) { sk_sp discardableManager = sk_make_sp(); 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 typeface) { SkFont font; font.setSubpixel(true); font.setSize(96); font.setHinting(SkFontHinting::kNormal); 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, std::size(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 < std::size(glyphs); i++) { runBuffer.pos[i] = i * 100; } return builder.make(); }; auto serverBlob = makeBlob(serverTf); auto props = FindSurfaceProps(ctxInfo.grContext()); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, ctxInfo.directContext()->supportsDistanceFieldText()); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 400, paint); std::vector 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()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } #endif DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextXY, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); 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(direct); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0.5, 0, paint); std::vector 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, direct, nullptr, 0.5); SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, direct, nullptr, 0.5); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } #if !defined(SK_DISABLE_SDF_TEXT) DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsDFT, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); if (!direct->priv().caps()->shaderCaps()->supportsDistanceFieldText()) { return; } sk_sp discardableManager = sk_make_sp(); SkStrikeServer server(discardableManager.get()); SkStrikeClient client(discardableManager, false); SkPaint paint; SkFont font; // A scale transform forces fallback to dft. SkMatrix matrix = SkMatrix::Scale(16, 16); sktext::gpu::SDFTControl control = direct->priv().asRecordingContext()->priv().getSDFTControl(true); SkScalar approximateDeviceTextSize = SkFontPriv::ApproximateTransformedTextSize(font, matrix, {0, 0}); REPORTER_ASSERT(reporter, control.isSDFT(approximateDeviceTextSize, paint, matrix)); // Server. auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle()); auto serverTfData = server.serializeTypeface(serverTf.get()); int glyphCount = 10; auto serverBlob = buildTextBlob(serverTf, glyphCount); const SkSurfaceProps props; std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 10, 10, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); cache_diff_canvas->concat(matrix); cache_diff_canvas->drawTextBlob(serverBlob.get(), 0, 0, paint); std::vector 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, direct, &matrix); SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint, direct, &matrix); compare_blobs(expected, actual, reporter); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } #endif // !defined(SK_DISABLE_SDF_TEXT) DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_CacheMissReporting, reporter, ctxInfo, CtsEnforcement::kNever) { sk_sp discardableManager = sk_make_sp(); 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.directContext(), &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(); } sk_sp MakeEmojiBlob(sk_sp serverTf, SkScalar textSize, sk_sp clientTf = nullptr) { SkFont font; font.setTypeface(serverTf); font.setSize(textSize); const char* text = ToolUtils::emoji_sample_text(); auto blob = SkTextBlob::MakeFromText(text, strlen(text), font); if (clientTf == nullptr) return blob; SkSerialProcs s_procs; s_procs.fTypefaceProc = [](SkTypeface*, void* ctx) -> sk_sp { return SkData::MakeUninitialized(1u); }; auto serialized = blob->serialize(s_procs); SkDeserialProcs d_procs; d_procs.fTypefaceCtx = &clientTf; d_procs.fTypefaceProc = [](const void* data, size_t length, void* ctx) -> sk_sp { return *(static_cast*>(ctx)); }; return SkTextBlob::Deserialize(serialized->data(), serialized->size(), d_procs); } DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_TypefaceWithNoPaths, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); SkStrikeServer server(discardableManager.get()); SkStrikeClient client(discardableManager, false); auto serverTf = ToolUtils::emoji_typeface(); auto serverTfData = server.serializeTypeface(serverTf.get()); auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size()); auto props = FindSurfaceProps(direct); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 500, 500, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); for (SkScalar textSize : { 70, 180, 270, 340}) { auto serverBlob = MakeEmojiBlob(serverTf, textSize); SkPaint paint; cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); std::vector serverStrikeData; server.writeStrikeData(&serverStrikeData); if (!serverStrikeData.empty()) { REPORTER_ASSERT(reporter, client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); } auto clientBlob = MakeEmojiBlob(serverTf, textSize, clientTf); REPORTER_ASSERT(reporter, clientBlob); RasterBlob(clientBlob, 500, 500, paint, direct); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); discardableManager->resetCacheMissCounts(); } // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } class SkRemoteGlyphCacheTest { public: static sk_sp MakeNormalBlob(SkPaint* paint, sk_sp serverTf, bool asPaths, SkScalar textSize, sk_sp clientTf = nullptr) { SkFont font; font.setTypeface(serverTf); font.setSize(textSize); const char* text = "Hel lo"; if (asPaths) { font.setupForAsPaths(paint); } else { SkFont font2(font); font2.setupForAsPaths(paint); } auto blob = SkTextBlob::MakeFromText(text, strlen(text), font); if (clientTf == nullptr) return blob; SkSerialProcs s_procs; s_procs.fTypefaceProc = [](SkTypeface*, void* ctx) -> sk_sp { return SkData::MakeUninitialized(1u); }; auto serialized = blob->serialize(s_procs); SkDeserialProcs d_procs; d_procs.fTypefaceCtx = &clientTf; d_procs.fTypefaceProc = [](const void* data, size_t length, void* ctx) -> sk_sp { return *(static_cast*>(ctx)); }; return SkTextBlob::Deserialize(serialized->data(), serialized->size(), d_procs); } }; DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_TypefaceWithPaths_MaskThenPath, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); SkStrikeServer server(discardableManager.get()); SkStrikeClient client(discardableManager, true); auto serverTf = ToolUtils::create_portable_typeface(); auto serverTfData = server.serializeTypeface(serverTf.get()); auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size()); auto props = FindSurfaceProps(direct); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 500, 500, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); SkPaint paint; using Rgct = SkRemoteGlyphCacheTest; // Draw from mask out of the strike which provides paths. { auto serverBlob = Rgct::MakeNormalBlob(&paint, serverTf, true, 64); cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); } // Draw from path out of the strike which provides paths. { auto serverBlob = Rgct::MakeNormalBlob(&paint, serverTf, false, 440); cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); } std::vector serverStrikeData; server.writeStrikeData(&serverStrikeData); if (!serverStrikeData.empty()) { REPORTER_ASSERT(reporter, client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); } { auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTf, true, 64, clientTf); REPORTER_ASSERT(reporter, clientBlob); RasterBlob(clientBlob, 100, 100, paint, direct); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); discardableManager->resetCacheMissCounts(); } { auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTf, false, 440, clientTf); REPORTER_ASSERT(reporter, clientBlob); RasterBlob(clientBlob, 100, 100, paint, direct); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); discardableManager->resetCacheMissCounts(); } // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_TypefaceWithPaths_PathThenMask, reporter, ctxInfo, CtsEnforcement::kNever) { auto direct = ctxInfo.directContext(); sk_sp discardableManager = sk_make_sp(); SkStrikeServer server(discardableManager.get()); SkStrikeClient client(discardableManager, true); auto serverTf = ToolUtils::create_portable_typeface(); auto serverTfData = server.serializeTypeface(serverTf.get()); auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size()); auto props = FindSurfaceProps(direct); std::unique_ptr cache_diff_canvas = server.makeAnalysisCanvas( 500, 500, props, nullptr, direct->supportsDistanceFieldText(), !direct->priv().caps()->disablePerspectiveSDFText()); SkPaint paint; using Rgct = SkRemoteGlyphCacheTest; // Draw from path out of the strike which provides paths. { auto serverBlob = Rgct::MakeNormalBlob(&paint, serverTf, false, 440); cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); } // Draw from mask out of the strike which provides paths. { auto serverBlob = Rgct::MakeNormalBlob(&paint, serverTf, true, 64); cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); } std::vector serverStrikeData; server.writeStrikeData(&serverStrikeData); if (!serverStrikeData.empty()) { REPORTER_ASSERT(reporter, client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); } { auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTf, true, 64, clientTf); REPORTER_ASSERT(reporter, clientBlob); RasterBlob(clientBlob, 100, 100, paint, direct); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); discardableManager->resetCacheMissCounts(); } { auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTf, false, 440, clientTf); REPORTER_ASSERT(reporter, clientBlob); RasterBlob(clientBlob, 100, 100, paint, direct); REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); discardableManager->resetCacheMissCounts(); } // Must unlock everything on termination, otherwise valgrind complains about memory leaks. discardableManager->unlockAndDeleteAll(); } #endif