| /* | |
| * Copyright 2018 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/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/SkFontMetrics.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/private/SkMalloc.h" | |
| #include "include/private/SkMutex.h" | |
| #include "include/private/chromium/SkChromeRemoteGlyphCache.h" | |
| #include "include/private/chromium/Slug.h" | |
| #include "src/core/SkFontMetricsPriv.h" | |
| #include "src/core/SkFontPriv.h" | |
| #include "src/core/SkGlyph.h" | |
| #include "src/core/SkReadBuffer.h" | |
| #include "src/core/SkScalerContext.h" | |
| #include "src/core/SkStrikeCache.h" | |
| #include "src/core/SkStrikeSpec.h" | |
| #include "src/core/SkTHash.h" | |
| #include "src/core/SkTextBlobPriv.h" | |
| #include "src/core/SkTypeface_remote.h" | |
| #include "src/core/SkWriteBuffer.h" | |
| #include "src/text/StrikeForGPU.h" | |
| #include "src/text/gpu/SlugImpl.h" | |
| #include "src/text/gpu/SubRunContainer.h" | |
| #include "src/text/gpu/SubRunControl.h" | |
| #include "tests/CtsEnforcement.h" | |
| #include "tests/Test.h" | |
| #include "tools/Resources.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include "tools/fonts/TestEmptyTypeface.h" | |
| #include "tools/text/gpu/TextBlobTools.h" | |
| #if defined(SK_GANESH) | |
| #include "include/gpu/ganesh/GrContextOptions.h" | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/GrRecordingContext.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.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" | |
| #endif | |
| #include <cmath> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <initializer_list> | |
| #include <memory> | |
| #include <optional> | |
| #include <vector> | |
| #include <type_traits> | |
| using namespace skia_private; | |
| 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 THashSet<SkDiscardableHandleId>& 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; | |
| THashSet<SkDiscardableHandleId> fLockedHandles; | |
| int fCacheMissCount[SkStrikeClient::CacheMissType::kLast + 1u]; | |
| }; | |
| sk_sp<SkTextBlob> buildTextBlob(sk_sp<SkTypeface> tf, int glyphCount, int textSize = 1) { | |
| SkFont font; | |
| SkASSERT(tf); | |
| 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<SkGlyphID>(i); | |
| runBuffer.pos[i] = SkIntToScalar(i); | |
| } | |
| return builder.make(); | |
| } | |
| #if defined(SK_GANESH) | |
| 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<SkSurface> MakeSurface(int width, int height, GrRecordingContext* rContext) { | |
| const SkImageInfo info = | |
| SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType); | |
| return SkSurfaces::RenderTarget(rContext, skgpu::Budgeted::kNo, info); | |
| } | |
| SkSurfaceProps FindSurfaceProps(GrRecordingContext* rContext) { | |
| auto surface = MakeSurface(1, 1, rContext); | |
| return surface->props(); | |
| } | |
| SkBitmap RasterBlob(sk_sp<SkTextBlob> 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<SkTextBlob> 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, paint); | |
| SkBitmap bitmap; | |
| bitmap.allocN32Pixels(width, height); | |
| surface->readPixels(bitmap, 0, 0); | |
| return bitmap; | |
| } | |
| SkBitmap RasterSlug(sk_sp<Slug> 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, paint); | |
| SkBitmap bitmap; | |
| bitmap.allocN32Pixels(width, height); | |
| surface->readPixels(bitmap, 0, 0); | |
| return bitmap; | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_StrikeSerialization, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| const SkPaint paint; | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| auto props = FindSurfaceProps(dContext); | |
| std::unique_ptr<SkCanvas> analysisCanvas = server.makeAnalysisCanvas( | |
| 10, 10, props, nullptr, dContext->supportsDistanceFieldText(), | |
| !dContext->priv().caps()->disablePerspectiveSDFText()); | |
| analysisCanvas->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 clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| // Ensure typeface serialization/deserialization worked. | |
| REPORTER_ASSERT(reporter, | |
| static_cast<SkTypefaceProxy*>(clientTypeface.get())->remoteTypefaceID() == | |
| serverTypefaceID); | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| static void use_padding_options(GrContextOptions* options) { | |
| options->fSupportBilerpFromGlyphAtlas = true; | |
| } | |
| DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_StrikeSerializationSlug, | |
| skgpu::IsRenderingContext, | |
| reporter, | |
| ctxInfo, | |
| use_padding_options, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| const SkPaint paint; | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| auto props = FindSurfaceProps(dContext); | |
| std::unique_ptr<SkCanvas> 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<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| // Client. | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_StrikeSerializationSlugForcePath, | |
| skgpu::IsRenderingContext, | |
| reporter, | |
| ctxInfo, | |
| use_padding_options, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| const SkPaint paint; | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount, 360); | |
| auto props = FindSurfaceProps(dContext); | |
| std::unique_ptr<SkCanvas> 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<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| // Client. | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_SlugSerialization, | |
| skgpu::IsRenderingContext, | |
| reporter, | |
| ctxInfo, | |
| use_padding_options, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| const SkPaint paint; | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| auto props = FindSurfaceProps(dContext); | |
| std::unique_ptr<SkCanvas> 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<uint8_t> 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.deserializeSlugForTest(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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_SlugSerialization_LargeOffset, | |
| skgpu::IsRenderingContext, | |
| reporter, | |
| ctxInfo, | |
| use_padding_options, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| const SkPaint paint; | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| auto props = FindSurfaceProps(dContext); | |
| std::unique_ptr<SkCanvas> analysisCanvas = server.makeAnalysisCanvas( | |
| 200, 200, props, nullptr, dContext->supportsDistanceFieldText(), | |
| !dContext->priv().caps()->disablePerspectiveSDFText()); | |
| // Generate slug with a large offset (e.g., 100, 100) | |
| auto srcSlug = Slug::ConvertBlob(analysisCanvas.get(), *serverBlob, {100.f, 100.f}, paint); | |
| auto dstSlugData = srcSlug->serialize(); | |
| std::vector<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto draw_with_clip = [&](sk_sp<Slug> slug) { | |
| auto surface = MakeSurface(200, 200, dContext); | |
| auto canvas = surface->getCanvas(); | |
| canvas->clipRect(SkRect::MakeLTRB(90, 90, 200, 200)); | |
| slug->draw(canvas, paint); | |
| SkBitmap bitmap; | |
| bitmap.allocN32Pixels(200, 200); | |
| surface->readPixels(bitmap, 0, 0); | |
| return bitmap; | |
| }; | |
| SkBitmap expected = draw_with_clip(srcSlug); | |
| auto dstSlug = client.deserializeSlugForTest(dstSlugData->data(), dstSlugData->size()); | |
| REPORTER_ASSERT(reporter, dstSlug != nullptr); | |
| SkBitmap actual = draw_with_clip(dstSlug); | |
| compare_blobs(expected, actual, reporter); | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_GANESH_TEST_FOR_CONTEXTS(SkRemoteGlyphCache_SubRunBoundsReconstruction, | |
| skgpu::IsRenderingContext, | |
| reporter, | |
| ctxInfo, | |
| use_padding_options, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| const SkPaint paint; | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| auto colrTypeface = ToolUtils::CreateTypefaceFromResource("fonts/colr.ttf"); | |
| if (!colrTypeface) { | |
| return; | |
| } | |
| auto props = FindSurfaceProps(dContext); | |
| auto check_bounds = [&](sk_sp<SkTextBlob> blob, const SkMatrix& matrix, const char* name) { | |
| std::unique_ptr<SkCanvas> analysisCanvas = | |
| server.makeAnalysisCanvas(500, | |
| 500, | |
| props, | |
| nullptr, | |
| dContext->supportsDistanceFieldText(), | |
| !dContext->priv().caps()->disablePerspectiveSDFText()); | |
| analysisCanvas->save(); | |
| analysisCanvas->concat(matrix); | |
| auto srcSlug = | |
| sktext::gpu::Slug::ConvertBlob(analysisCanvas.get(), *blob, {0.f, 0.f}, paint); | |
| analysisCanvas->restore(); | |
| REPORTER_ASSERT(reporter, srcSlug != nullptr, "Slug is null for %s", name); | |
| auto dstSlugData = srcSlug->serialize(); | |
| std::vector<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto dstSlug = client.deserializeSlugForTest(dstSlugData->data(), dstSlugData->size()); | |
| REPORTER_ASSERT(reporter, dstSlug != nullptr, "Dst slug is null for %s", name); | |
| auto dstSlugImpl = static_cast<sktext::gpu::SlugImpl*>(dstSlug.get()); | |
| const sktext::gpu::SubRunContainer* dstContainer = dstSlugImpl->subRuns().get(); | |
| REPORTER_ASSERT(reporter, dstContainer != nullptr, "Dst container is null for %s", name); | |
| const sktext::gpu::AtlasSubRun* dstSubRun = | |
| sktext::gpu::TextBlobTools::FirstSubRun(dstContainer); | |
| if (dstSubRun) { | |
| auto dstRect = std::get<1>(dstSubRun->deviceRectAndNeedsTransform(matrix)); | |
| REPORTER_ASSERT(reporter, | |
| !dstRect.isEmpty(), | |
| "Dst bounds failed to reconstruct (empty) for %s", | |
| name); | |
| SkRect mappedBlobBounds = blob->bounds(); | |
| matrix.mapRect(&mappedBlobBounds); | |
| REPORTER_ASSERT(reporter, | |
| mappedBlobBounds.contains(dstRect), | |
| "Mapped blob bounds [%f, %f, %f, %f] do not contain reconstructed " | |
| "bounds [%f, %f, %f, %f] for %s", | |
| mappedBlobBounds.fLeft, | |
| mappedBlobBounds.fTop, | |
| mappedBlobBounds.fRight, | |
| mappedBlobBounds.fBottom, | |
| dstRect.fLeft, | |
| dstRect.fTop, | |
| dstRect.fRight, | |
| dstRect.fBottom, | |
| name); | |
| SkTextBlobRunIterator it(blob.get()); | |
| while (!it.done()) { | |
| SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning); | |
| SkFont font = it.font(); | |
| int count = it.glyphCount(); | |
| const uint16_t* glyphs = it.glyphs(); | |
| const SkPoint* pos = reinterpret_cast<const SkPoint*>(it.pos()); | |
| std::vector<SkRect> glyphBounds(count); | |
| font.getBounds(SkSpan<const SkGlyphID>(glyphs, count), | |
| SkSpan<SkRect>(glyphBounds.data(), count), | |
| nullptr); | |
| // outset the dstRect to account for rounding or SDF text (has 2 pixels of padding) | |
| SkScalar outset = dstSubRun->glyphParams().isSDF ? 2.5f : 1.5f; | |
| dstRect.outset(outset, outset); | |
| for (int i = 0; i < count; ++i) { | |
| SkRect mappedGlyphRect = glyphBounds[i]; | |
| if (mappedGlyphRect.isEmpty()) { | |
| continue; | |
| } | |
| mappedGlyphRect.offset(pos[i]); | |
| matrix.mapRect(&mappedGlyphRect); | |
| REPORTER_ASSERT(reporter, | |
| dstRect.contains(mappedGlyphRect), | |
| "Reconstructed bounds [%f, %f, %f, %f] do not contain glyph %d " | |
| "[%f, %f, %f, %f] in %s", | |
| dstRect.fLeft, | |
| dstRect.fTop, | |
| dstRect.fRight, | |
| dstRect.fBottom, | |
| i, | |
| mappedGlyphRect.fLeft, | |
| mappedGlyphRect.fTop, | |
| mappedGlyphRect.fRight, | |
| mappedGlyphRect.fBottom, | |
| name); | |
| } | |
| it.next(); | |
| } | |
| } else if (!dstContainer->isEmpty()) { | |
| INFOF(reporter, "GlyphRun was drawn with drawable or as a path in %s", name); | |
| } else { | |
| ERRORF(reporter, "Dst subrun is null for %s", name); | |
| } | |
| discardableManager->unlockAndDeleteAll(); | |
| }; | |
| auto build_blob = [](sk_sp<SkTypeface> tf, SkScalar size, const char* text = "abcdefgh") { | |
| SkFont font(tf, size); | |
| int len = strlen(text); | |
| int count = font.countText(text, len, SkTextEncoding::kUTF8); | |
| SkTextBlobBuilder builder; | |
| const auto& runBuffer = builder.allocRunPos(font, count); | |
| font.textToGlyphs( | |
| text, len, SkTextEncoding::kUTF8, SkSpan<SkGlyphID>(runBuffer.glyphs, count)); | |
| font.getPos(SkSpan<const SkGlyphID>(runBuffer.glyphs, count), | |
| SkSpan<SkPoint>(reinterpret_cast<SkPoint*>(runBuffer.pos), count), | |
| {0, 0}); | |
| return builder.make(); | |
| }; | |
| auto directBlob = build_blob(serverTypeface, 20); | |
| check_bounds(directBlob, SkMatrix::I(), "DirectMaskSubRun"); | |
| if (dContext->supportsDistanceFieldText()) { | |
| SkScalar sdftSize = dContext->priv().options().fGlyphsAsPathsFontSize / 2.f; | |
| auto sdftBlob = build_blob(serverTypeface, sdftSize); | |
| check_bounds(sdftBlob, SkMatrix::I(), "SDFTSubRun"); | |
| } | |
| auto hugeBlob = build_blob(colrTypeface, 20, "\U0001F600"); | |
| SkMatrix perspective = SkMatrix::I(); | |
| perspective.setPerspX(0.001f); | |
| perspective.setPerspY(0.001f); | |
| check_bounds(hugeBlob, perspective, "TransformedMaskSubRun"); | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_ReleaseTypeFace, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| // Server. | |
| auto serverTypeface = TestEmptyTypeface::Make(); | |
| REPORTER_ASSERT(reporter, serverTypeface->unique()); | |
| { | |
| const SkPaint paint; | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> 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, !serverTypeface->unique()); | |
| std::vector<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| } | |
| REPORTER_ASSERT(reporter, serverTypeface->unique()); | |
| // Must unlock everything on termination, otherwise memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| #endif | |
| DEF_TEST(SkRemoteGlyphCache_StrikeLockingServer, reporter) { | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> 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<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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_TEST(SkRemoteGlyphCache_StrikeDeletionServer, reporter) { | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> 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<uint8_t> 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 memory leaks can be reported. | |
| 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 serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> cache_diff_canvas = | |
| server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); | |
| 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* clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID).get(); | |
| // The cache remains alive until it is pinned in the discardable manager. | |
| SkGraphics::PurgeFontCache(); | |
| REPORTER_ASSERT(reporter, !clientTypeface->unique()); | |
| // Once the strike is unpinned and purged, SkStrikeClient should be the only owner of the | |
| // clientTf. | |
| discardableManager->unlockAndDeleteAll(); | |
| SkGraphics::PurgeFontCache(); | |
| REPORTER_ASSERT(reporter, clientTypeface->unique()); | |
| // Must unlock everything on termination, otherwise memory leaks can be reported. | |
| 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 serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> cache_diff_canvas = | |
| server.makeAnalysisCanvas(10, 10, props, nullptr, true, true); | |
| 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())); | |
| // Must unlock everything on termination, otherwise memory leaks can be reported. | |
| 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 serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> 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<uint8_t> fontData; | |
| server.writeStrikeData(&fontData); | |
| discardableManager->unlockAndDeleteAll(); | |
| // Use a different typeface. Creating a new strike should evict the previous | |
| // one. | |
| { | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("Georgia", SkFontStyle()); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| #if defined(SK_GANESH) | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsPath, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| auto direct = ctxInfo.directContext(); | |
| 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); | |
| SkFont font = ToolUtils::DefaultFont(); | |
| REPORTER_ASSERT(reporter, | |
| SkStrikeSpec::ShouldDrawAsPath(paint, font, SkMatrix::I())); | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| auto props = FindSurfaceProps(direct); | |
| std::unique_ptr<SkCanvas> 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<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| // Client. | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| sk_sp<SkTextBlob> make_blob_causing_fallback( | |
| sk_sp<SkTypeface> targetTf, const SkTypeface* glyphTf, skiatest::Reporter* reporter) { | |
| SkFont font = ToolUtils::DefaultFont(); | |
| 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"; | |
| size_t 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.getBounds(runBuffer.glyphs[0], nullptr); | |
| REPORTER_ASSERT(reporter, glyphBounds.width() > SkGlyphDigest::kSkSideTooBigForAtlas); | |
| for (size_t 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> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| SkPaint paint; | |
| auto serverTypeface = ToolUtils::CreateTypefaceFromResource("fonts/HangingS.ttf"); | |
| // TODO: when the cq bots can handle this font remove the check. | |
| if (serverTypeface == nullptr) { | |
| return; | |
| } | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| auto serverBlob = make_blob_causing_fallback(serverTypeface, serverTypeface.get(), reporter); | |
| auto props = FindSurfaceProps(direct); | |
| std::unique_ptr<SkCanvas> 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<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| // Client. | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = make_blob_causing_fallback(clientTypeface, serverTypeface.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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextXY, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| auto direct = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| auto props = FindSurfaceProps(direct); | |
| std::unique_ptr<SkCanvas> 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<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| // Client. | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| 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> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| SkPaint paint; | |
| SkFont font = ToolUtils::DefaultFont(); | |
| // A scale transform forces fallback to dft. | |
| SkMatrix matrix = SkMatrix::Scale(16, 16); | |
| sktext::gpu::SubRunControl control = | |
| direct->priv().asRecordingContext()->priv().getSubRunControl(true); | |
| SkScalar approximateDeviceTextSize = SkFontPriv::ApproximateTransformedTextSize(font, matrix, | |
| {0, 0}); | |
| REPORTER_ASSERT(reporter, control.isSDFT(approximateDeviceTextSize, paint, matrix)); | |
| // Server. | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| int glyphCount = 10; | |
| auto serverBlob = buildTextBlob(serverTypeface, glyphCount); | |
| const SkSurfaceProps props; | |
| std::unique_ptr<SkCanvas> 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<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| // Client. | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), serverStrikeData.size())); | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| #endif // !defined(SK_DISABLE_SDF_TEXT) | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_CacheMissReporting, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| auto serverTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| REPORTER_ASSERT(reporter, serverTypeface); | |
| // Create the clientTypeface proxy directly from the serverTypeface. | |
| auto clientTypeface = sk_make_sp<SkTypefaceProxy>( | |
| serverTypeface->uniqueID(), | |
| serverTypeface->countGlyphs(), | |
| serverTypeface->fontStyle(), | |
| serverTypeface->isFixedPitch(), | |
| /*glyphMaskNeedsCurrentColor=*/ false, | |
| discardableManager, | |
| /*isLogging=*/ false); | |
| REPORTER_ASSERT(reporter, clientTypeface); | |
| int glyphCount = 10; | |
| auto clientBlob = buildTextBlob(clientTypeface, 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 memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| sk_sp<SkTextBlob> MakeEmojiBlob(sk_sp<SkTypeface> serverTf, SkScalar textSize, | |
| sk_sp<SkTypeface> clientTf = nullptr) { | |
| SkFont font = ToolUtils::DefaultFont(); | |
| font.setTypeface(serverTf); | |
| font.setSize(textSize); | |
| const char* text = ToolUtils::EmojiSample().sampleText; | |
| auto blob = SkTextBlob::MakeFromText(text, strlen(text), font); | |
| if (clientTf == nullptr) return blob; | |
| SkSerialProcs s_procs; | |
| s_procs.fTypefaceProc = [](SkTypeface*, void* ctx) -> sk_sp<const SkData> { | |
| return SkData::MakeUninitialized(1u); | |
| }; | |
| auto serialized = blob->serialize(s_procs); | |
| SkDeserialProcs d_procs; | |
| d_procs.fTypefaceCtx = &clientTf; | |
| d_procs.fTypefaceStreamProc = [](SkStream& stream, void* ctx) -> sk_sp<SkTypeface> { | |
| char u; | |
| if (stream.read(&u, 1) != 1) { | |
| return nullptr; | |
| } | |
| return *(static_cast<sk_sp<SkTypeface>*>(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> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, false); | |
| auto serverTypeface = ToolUtils::EmojiSample().typeface; | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| auto props = FindSurfaceProps(direct); | |
| std::unique_ptr<SkCanvas> 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(serverTypeface, textSize); | |
| SkPaint paint; | |
| cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); | |
| std::vector<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| if (!serverStrikeData.empty()) { | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), | |
| serverStrikeData.size())); | |
| } | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| auto clientBlob = MakeEmojiBlob(serverTypeface, textSize, clientTypeface); | |
| REPORTER_ASSERT(reporter, clientBlob); | |
| RasterBlob(clientBlob, 500, 500, paint, direct); | |
| REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); | |
| discardableManager->resetCacheMissCounts(); | |
| } | |
| // Must unlock everything on termination, otherwise memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| class SkRemoteGlyphCacheTest { | |
| public: | |
| static sk_sp<SkTextBlob> MakeNormalBlob(SkPaint* paint, | |
| sk_sp<SkTypeface> serverTf, bool asPaths, SkScalar textSize, | |
| sk_sp<SkTypeface> clientTf = nullptr) { | |
| SkFont font = ToolUtils::DefaultFont(); | |
| 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<const SkData> { | |
| return SkData::MakeUninitialized(1u); | |
| }; | |
| auto serialized = blob->serialize(s_procs); | |
| SkDeserialProcs d_procs; | |
| d_procs.fTypefaceCtx = &clientTf; | |
| d_procs.fTypefaceStreamProc = [](SkStream& stream, void* ctx) -> sk_sp<SkTypeface> { | |
| char u; | |
| if (stream.read(&u, 1) != 1) { | |
| return nullptr; | |
| } | |
| return *(static_cast<sk_sp<SkTypeface>*>(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> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, true); | |
| auto serverTypeface = ToolUtils::DefaultPortableTypeface(); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| auto props = FindSurfaceProps(direct); | |
| std::unique_ptr<SkCanvas> 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, serverTypeface, 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, serverTypeface, false, 440); | |
| cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); | |
| } | |
| std::vector<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| if (!serverStrikeData.empty()) { | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), | |
| serverStrikeData.size())); | |
| } | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| { | |
| auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTypeface, true, 64, clientTypeface); | |
| REPORTER_ASSERT(reporter, clientBlob); | |
| RasterBlob(clientBlob, 100, 100, paint, direct); | |
| REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); | |
| discardableManager->resetCacheMissCounts(); | |
| } | |
| { | |
| auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTypeface, false, 440, clientTypeface); | |
| REPORTER_ASSERT(reporter, clientBlob); | |
| RasterBlob(clientBlob, 100, 100, paint, direct); | |
| REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); | |
| discardableManager->resetCacheMissCounts(); | |
| } | |
| // Must unlock everything on termination, otherwise memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_TypefaceWithPaths_PathThenMask, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| auto direct = ctxInfo.directContext(); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeServer server(discardableManager.get()); | |
| SkStrikeClient client(discardableManager, true); | |
| auto serverTypeface = ToolUtils::DefaultPortableTypeface(); | |
| const SkTypefaceID serverTypefaceID = serverTypeface->uniqueID(); | |
| auto props = FindSurfaceProps(direct); | |
| std::unique_ptr<SkCanvas> 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, serverTypeface, 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, serverTypeface, true, 64); | |
| cache_diff_canvas->drawTextBlob(serverBlob.get(), 100, 100, paint); | |
| } | |
| std::vector<uint8_t> serverStrikeData; | |
| server.writeStrikeData(&serverStrikeData); | |
| if (!serverStrikeData.empty()) { | |
| REPORTER_ASSERT(reporter, | |
| client.readStrikeData(serverStrikeData.data(), | |
| serverStrikeData.size())); | |
| } | |
| auto clientTypeface = client.retrieveTypefaceUsingServerIDForTest(serverTypefaceID); | |
| { | |
| auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTypeface, true, 64, clientTypeface); | |
| REPORTER_ASSERT(reporter, clientBlob); | |
| RasterBlob(clientBlob, 100, 100, paint, direct); | |
| REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); | |
| discardableManager->resetCacheMissCounts(); | |
| } | |
| { | |
| auto clientBlob = Rgct::MakeNormalBlob(&paint, serverTypeface, false, 440, clientTypeface); | |
| REPORTER_ASSERT(reporter, clientBlob); | |
| RasterBlob(clientBlob, 100, 100, paint, direct); | |
| REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss()); | |
| discardableManager->resetCacheMissCounts(); | |
| } | |
| // Must unlock everything on termination, otherwise memory leaks can be reported. | |
| discardableManager->unlockAndDeleteAll(); | |
| } | |
| #endif | |
| DEF_TEST(SkTypefaceProxy_Basic_Serial, reporter) { | |
| auto typeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); | |
| sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkTypefaceProxyPrototype srcProto{*typeface}; | |
| SkBinaryWriteBuffer writeBuffer({}); | |
| srcProto.flatten(writeBuffer); | |
| auto data = writeBuffer.snapshotAsData(); | |
| SkReadBuffer readBuffer{data->data(), data->size()}; | |
| std::optional<SkTypefaceProxyPrototype> dstProto = | |
| SkTypefaceProxyPrototype::MakeFromBuffer(readBuffer); | |
| REPORTER_ASSERT(reporter, dstProto.has_value()); | |
| auto proxy = sk_make_sp<SkTypefaceProxy>(dstProto.value(), discardableManager, false); | |
| REPORTER_ASSERT(reporter, typeface->uniqueID() == proxy->remoteTypefaceID()); | |
| REPORTER_ASSERT(reporter, typeface->uniqueID() != proxy->uniqueID()); | |
| REPORTER_ASSERT(reporter, typeface->countGlyphs() == proxy->countGlyphs()); | |
| REPORTER_ASSERT(reporter, typeface->fontStyle() == proxy->fontStyle()); | |
| REPORTER_ASSERT(reporter, typeface->isFixedPitch() == proxy->isFixedPitch()); | |
| // Must be multiple of 4 bytes or the buffer will be invalid. | |
| uint8_t brokenBytes[] = {1, 2, 3, 4, 5, 6, 7, 8}; | |
| SkReadBuffer brokenBuffer{std::data(brokenBytes), std::size(brokenBytes)}; | |
| std::optional<SkTypefaceProxyPrototype> brokenProto = | |
| SkTypefaceProxyPrototype::MakeFromBuffer(brokenBuffer); | |
| REPORTER_ASSERT(reporter, !brokenProto.has_value()); | |
| } | |
| DEF_TEST(SkGraphics_Limits, reporter) { | |
| const auto prev1 = SkGraphics::GetTypefaceCacheCountLimit(); | |
| auto prev2 = SkGraphics::SetTypefaceCacheCountLimit(prev1 + 1); | |
| REPORTER_ASSERT(reporter, prev1 == prev2); | |
| prev2 = SkGraphics::GetTypefaceCacheCountLimit(); | |
| REPORTER_ASSERT(reporter, prev2 == prev1 + 1); | |
| SkGraphics::SetTypefaceCacheCountLimit(prev1); // restore orig | |
| } | |
| DEF_TEST(SkRemoteGlyphCache_b513780208, reporter) { | |
| // Legitimate path glyphs should only use kBW_Format, kA8_Format, or kLCD16_Format. | |
| // If a malicious strike provided a path with kARGB32_Format, it could trigger an | |
| // uninitialized memory issue in SkScalerContext::GenerateImageFromPath. | |
| // | |
| // SkGlyph::addPathFromBuffer now rejects any path whose format is not an allowed type. | |
| constexpr int kW = 200; | |
| constexpr int kH = 4; | |
| constexpr SkTypefaceID kServerTypefaceID = 0x65000000u; // arbitrary | |
| constexpr uint32_t kPackedGlyphID = 0x42u; // arbitrary | |
| auto BuildMaliciousStrikeData = [&]() { | |
| SkBinaryWriteBuffer buffer{nullptr, 0, {}}; | |
| // --- typefaces --- | |
| buffer.writeInt(1); // typefaceCount | |
| buffer.writeUInt(kServerTypefaceID); | |
| buffer.writeInt(256); | |
| buffer.write32(0); | |
| buffer.writeBool(false); | |
| buffer.writeBool(false); | |
| // --- strikes --- | |
| buffer.writeInt(1); // strikeCount | |
| buffer.writeUInt(kServerTypefaceID); // serverTypefaceID | |
| buffer.writeUInt(1); // discardableHandleID | |
| // SkDescriptor: Craft a Rec that triggers GenerateImageFromPath (fFrameWidth >= 0). | |
| { | |
| SkScalerContextRec rec{}; | |
| rec.fTypefaceID = kServerTypefaceID; | |
| rec.fTextSize = 16.0f; | |
| rec.fPreScaleX = 1.0f; | |
| rec.fPost2x2[0][0] = 1.0f; | |
| rec.fPost2x2[1][1] = 1.0f; | |
| rec.fFrameWidth = 0.0f; // triggers fGenerateImageFromPath | |
| rec.fMaskFormat = SkMask::kA8_Format; | |
| SkAutoDescriptor ad{SkDescriptor::ComputeOverhead(1) + sizeof(rec)}; | |
| SkDescriptor* desc = ad.getDesc(); | |
| desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec); | |
| desc->computeChecksum(); | |
| desc->flatten(buffer); | |
| } | |
| buffer.writeBool(false); // fontMetricsInitialized == false | |
| { | |
| SkFontMetrics fm{}; | |
| SkFontMetricsPriv::Flatten(buffer, fm); | |
| } | |
| buffer.writeInt(0); // imagesCount | |
| // Craft a path glyph with an invalid mask format (kARGB32_Format). | |
| buffer.writeInt(1); // pathsCount | |
| buffer.writeUInt(kPackedGlyphID); | |
| buffer.writePoint(SkPoint::Make(static_cast<float>(kW), 0.0f)); | |
| buffer.writeUInt((static_cast<uint32_t>(kW) << 16) | kH); | |
| buffer.writeUInt(0); | |
| buffer.writeUInt(static_cast<uint32_t>(SkMask::kARGB32_Format)); | |
| buffer.writeBool(true); // hasPath | |
| buffer.writeBool(false); | |
| buffer.writeBool(false); | |
| buffer.writePath(SkPath::Rect(SkRect::MakeXYWH(0, 0, 1, 1))); | |
| buffer.writeInt(0); // drawablesCount | |
| return buffer.snapshotAsData(); | |
| }; | |
| sk_sp<SkData> blob = BuildMaliciousStrikeData(); | |
| REPORTER_ASSERT(reporter, blob); | |
| SkStrikeCache cache; | |
| auto discardableManager = sk_make_sp<DiscardableManager>(); | |
| SkStrikeClient client(discardableManager, /*isLogging=*/false, &cache); | |
| // This should fail to read the strike data because SkGlyph::addPathFromBuffer | |
| // now validates that path glyphs have an expected mask format. | |
| REPORTER_ASSERT(reporter, !client.readStrikeData(blob->data(), blob->size())); | |
| } |