|  | /* | 
|  | * Copyright 2020 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/SkColorType.h" | 
|  | #include "include/core/SkFont.h" | 
|  | #include "include/core/SkFontStyle.h" | 
|  | #include "include/core/SkFontTypes.h" | 
|  | #include "include/core/SkImageInfo.h" | 
|  | #include "include/core/SkMatrix.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPoint.h" | 
|  | #include "include/core/SkRefCnt.h" | 
|  | #include "include/core/SkScalar.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/GrDirectContext.h" | 
|  | #include "include/gpu/ganesh/SkSurfaceGanesh.h" | 
|  | #include "src/core/SkDevice.h" | 
|  | #include "src/core/SkScalerContext.h" | 
|  | #include "src/text/GlyphRun.h" | 
|  | #include "src/text/gpu/SDFTControl.h" | 
|  | #include "src/text/gpu/SubRunAllocator.h" | 
|  | #include "src/text/gpu/TextBlob.h" | 
|  | #include "tests/CtsEnforcement.h" | 
|  | #include "tests/Test.h" | 
|  | #include "tools/ToolUtils.h" | 
|  | #include "tools/fonts/FontToolUtils.h" | 
|  |  | 
|  | #include <cmath> | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <limits> | 
|  | #include <memory> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  |  | 
|  | class GrRecordingContext; | 
|  | struct GrContextOptions; | 
|  |  | 
|  | using BagOfBytes = sktext::gpu::BagOfBytes; | 
|  | using SubRunAllocator = sktext::gpu::SubRunAllocator; | 
|  |  | 
|  | SkBitmap rasterize_blob(SkTextBlob* blob, | 
|  | const SkPaint& paint, | 
|  | GrRecordingContext* rContext, | 
|  | const SkMatrix& matrix) { | 
|  | const SkImageInfo info = | 
|  | SkImageInfo::Make(500, 500, kN32_SkColorType, kPremul_SkAlphaType); | 
|  | auto surface = SkSurfaces::RenderTarget(rContext, skgpu::Budgeted::kNo, info); | 
|  | auto canvas = surface->getCanvas(); | 
|  | canvas->drawColor(SK_ColorWHITE); | 
|  | canvas->concat(matrix); | 
|  | canvas->drawTextBlob(blob, 10, 250, paint); | 
|  | SkBitmap bitmap; | 
|  | bitmap.allocN32Pixels(500, 500); | 
|  | surface->readPixels(bitmap, 0, 0); | 
|  | return bitmap; | 
|  | } | 
|  |  | 
|  | bool check_for_black(const SkBitmap& bm) { | 
|  | for (int y = 0; y < bm.height(); y++) { | 
|  | for (int x = 0; x < bm.width(); x++) { | 
|  | if (bm.getColor(x, y) == SK_ColorBLACK) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrTextBlobScaleAnimation, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kApiLevel_T) { | 
|  | auto tf = ToolUtils::CreatePortableTypeface("Mono", SkFontStyle()); | 
|  | SkFont font{tf}; | 
|  | font.setHinting(SkFontHinting::kNormal); | 
|  | font.setSize(12); | 
|  | font.setEdging(SkFont::Edging::kAntiAlias); | 
|  | font.setSubpixel(true); | 
|  |  | 
|  | SkTextBlobBuilder builder; | 
|  | const auto& runBuffer = builder.allocRunPosH(font, 30, 0, nullptr); | 
|  |  | 
|  | for (int i = 0; i < 30; i++) { | 
|  | runBuffer.glyphs[i] = static_cast<SkGlyphID>(i); | 
|  | runBuffer.pos[i] = SkIntToScalar(i); | 
|  | } | 
|  | auto blob = builder.make(); | 
|  |  | 
|  | auto dContext = ctxInfo.directContext(); | 
|  | bool anyBlack = false; | 
|  | for (int n = -13; n < 5; n++) { | 
|  | SkMatrix m = SkMatrix::Scale(std::exp2(n), std::exp2(n)); | 
|  | auto bm = rasterize_blob(blob.get(), SkPaint(), dContext, m); | 
|  | anyBlack |= check_for_black(bm); | 
|  | } | 
|  | REPORTER_ASSERT(reporter, anyBlack); | 
|  | } | 
|  |  | 
|  | // Test extreme positions for all combinations of positions, origins, and translation matrices. | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrTextBlobMoveAround, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kApiLevel_T) { | 
|  | auto tf = ToolUtils::CreatePortableTypeface("Mono", SkFontStyle()); | 
|  | SkFont font{tf}; | 
|  | font.setHinting(SkFontHinting::kNormal); | 
|  | font.setSize(12); | 
|  | font.setEdging(SkFont::Edging::kAntiAlias); | 
|  | font.setSubpixel(true); | 
|  |  | 
|  | auto makeBlob = [&](SkPoint delta) { | 
|  | SkTextBlobBuilder builder; | 
|  | const auto& runBuffer = builder.allocRunPos(font, 30, nullptr); | 
|  |  | 
|  | for (int i = 0; i < 30; i++) { | 
|  | runBuffer.glyphs[i] = static_cast<SkGlyphID>(i); | 
|  | runBuffer.points()[i] = SkPoint::Make(SkIntToScalar(i*10) + delta.x(), 50 + delta.y()); | 
|  | } | 
|  | return builder.make(); | 
|  | }; | 
|  |  | 
|  | auto dContext = ctxInfo.directContext(); | 
|  | auto rasterizeBlob = [&](SkTextBlob* blob, SkPoint origin, const SkMatrix& matrix) { | 
|  | SkPaint paint; | 
|  | const SkImageInfo info = | 
|  | SkImageInfo::Make(350, 80, kN32_SkColorType, kPremul_SkAlphaType); | 
|  | auto surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info); | 
|  | auto canvas = surface->getCanvas(); | 
|  | canvas->drawColor(SK_ColorWHITE); | 
|  | canvas->concat(matrix); | 
|  | canvas->drawTextBlob(blob, 10 + origin.x(), 40 + origin.y(), paint); | 
|  | SkBitmap bitmap; | 
|  | bitmap.allocN32Pixels(350, 80); | 
|  | surface->readPixels(bitmap, 0, 0); | 
|  | return bitmap; | 
|  | }; | 
|  |  | 
|  | SkBitmap benchMark; | 
|  | { | 
|  | auto blob = makeBlob({0, 0}); | 
|  | benchMark = rasterizeBlob(blob.get(), {0,0}, SkMatrix::I()); | 
|  | } | 
|  |  | 
|  | auto checkBitmap = [&](const SkBitmap& bitmap) { | 
|  | REPORTER_ASSERT(reporter, benchMark.width() == bitmap.width()); | 
|  | REPORTER_ASSERT(reporter, benchMark.width() == bitmap.width()); | 
|  |  | 
|  | for (int y = 0; y < benchMark.height(); y++) { | 
|  | for (int x = 0; x < benchMark.width(); x++) { | 
|  | if (benchMark.getColor(x, y) != bitmap.getColor(x, y)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | SkScalar interestingNumbers[] = {-10'000'000, -1'000'000, -1, 0, +1, +1'000'000, +10'000'000}; | 
|  | for (auto originX : interestingNumbers) { | 
|  | for (auto originY : interestingNumbers) { | 
|  | for (auto translateX : interestingNumbers) { | 
|  | for (auto translateY : interestingNumbers) { | 
|  | // Make sure everything adds to zero. | 
|  | SkScalar deltaPosX = -(originX + translateX); | 
|  | SkScalar deltaPosY = -(originY + translateY); | 
|  | auto blob = makeBlob({deltaPosX, deltaPosY}); | 
|  | SkMatrix t = SkMatrix::Translate(translateX, translateY); | 
|  | auto bitmap = rasterizeBlob(blob.get(), {originX, originY}, t); | 
|  | REPORTER_ASSERT(reporter, checkBitmap(bitmap)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | DEF_TEST(BagOfBytesBasic, r) { | 
|  | const int k4K = 1 << 12; | 
|  | { | 
|  | // GrBagOfBytes::MinimumSizeWithOverhead(-1); // This should fail | 
|  | BagOfBytes::PlatformMinimumSizeWithOverhead(0, 16); | 
|  | BagOfBytes::PlatformMinimumSizeWithOverhead( | 
|  | std::numeric_limits<int>::max() - k4K - 1, 16); | 
|  | // GrBagOfBytes::MinimumSizeWithOverhead(std::numeric_limits<int>::max() - k4K);  // Fail | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(0, 1, 16, 16) == 31); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(1, 1, 16, 16) == 32); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(63, 1, 16, 16) == 94); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(0, 8, 16, 16) == 24); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(1, 8, 16, 16) == 32); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(63, 8, 16, 16) == 88); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(0, 16, 16, 16) == 16); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(1, 16, 16, 16) == 32); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(63, 16, 16, 16) == 80); | 
|  |  | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(0, 1, 8, 16) == 23); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(1, 1, 8, 16) == 24); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(63, 1, 8, 16) == 86); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(0, 8, 8, 16) == 16); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(1, 8, 8, 16) == 24); | 
|  | REPORTER_ASSERT(r, BagOfBytes::MinimumSizeWithOverhead(63, 8, 8, 16) == 80); | 
|  | } | 
|  |  | 
|  | { | 
|  | BagOfBytes bob; | 
|  | // bob.alignedBytes(0, 1);  // This should fail | 
|  | // bob.alignedBytes(1, 0);  // This should fail | 
|  | // bob.alignedBytes(1, 3);  // This should fail | 
|  |  | 
|  | struct Big { | 
|  | char stuff[std::numeric_limits<int>::max()]; | 
|  | }; | 
|  | // bob.alignedBytes(sizeof(Big), 1);  // this should fail | 
|  | // bob.allocateBytesFor<Big>();  // this should not compile | 
|  | // The following should run, but should not be regularly tested. | 
|  | // bob.allocateBytesFor<int>((std::numeric_limits<int>::max() - (1<<12)) / sizeof(int) - 1); | 
|  | // The following should fail | 
|  | // bob.allocateBytesFor<int>((std::numeric_limits<int>::max() - (1<<12)) / sizeof(int)); | 
|  | bob.alignedBytes(1, 1);  // To avoid unused variable problems. | 
|  | } | 
|  |  | 
|  | // Force multiple block allocation | 
|  | { | 
|  | BagOfBytes bob; | 
|  | const int k64K = 1 << 16; | 
|  | // By default allocation block sizes start at 1K and go up with fib. This should allocate | 
|  | // 10 individual blocks. | 
|  | for (int i = 0; i < 10; i++) { | 
|  | bob.alignedBytes(k64K, 1); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | DEF_TEST(SubRunAllocator, r) { | 
|  | static int created = 0; | 
|  | static int destroyed = 0; | 
|  | struct Foo { | 
|  | Foo() : fI{-2}, fX{-3} { created++; } | 
|  | Foo(int i, float x) : fI{i}, fX{x} { created++; } | 
|  | ~Foo() { destroyed++; } | 
|  | int fI; | 
|  | float fX; | 
|  | }; | 
|  |  | 
|  | struct alignas(8) OddAlignment { | 
|  | char buf[10]; | 
|  | }; | 
|  |  | 
|  | auto exercise = [&](SubRunAllocator* alloc) { | 
|  | created = 0; | 
|  | destroyed = 0; | 
|  | { | 
|  | int* p = alloc->makePOD<int>(3); | 
|  | REPORTER_ASSERT(r, *p == 3); | 
|  | int* q = alloc->makePOD<int>(7); | 
|  | REPORTER_ASSERT(r, *q == 7); | 
|  |  | 
|  | REPORTER_ASSERT(r, *alloc->makePOD<int>(3) == 3); | 
|  | auto foo = alloc->makeUnique<Foo>(3, 4.0f); | 
|  | REPORTER_ASSERT(r, foo->fI == 3); | 
|  | REPORTER_ASSERT(r, foo->fX == 4.0f); | 
|  | REPORTER_ASSERT(r, created == 1); | 
|  | REPORTER_ASSERT(r, destroyed == 0); | 
|  |  | 
|  | alloc->makePODArray<int>(10); | 
|  |  | 
|  | auto fooArray = alloc->makeUniqueArray<Foo>(10); | 
|  | REPORTER_ASSERT(r, fooArray[3].fI == -2); | 
|  | REPORTER_ASSERT(r, fooArray[4].fX == -3.0f); | 
|  | REPORTER_ASSERT(r, created == 11); | 
|  | REPORTER_ASSERT(r, destroyed == 0); | 
|  | alloc->makePOD<OddAlignment>(); | 
|  | } | 
|  |  | 
|  | REPORTER_ASSERT(r, created == 11); | 
|  | REPORTER_ASSERT(r, destroyed == 11); | 
|  | }; | 
|  |  | 
|  | // Exercise default arena | 
|  | { | 
|  | SubRunAllocator arena{0}; | 
|  | exercise(&arena); | 
|  | } | 
|  |  | 
|  | // Exercise on stack arena | 
|  | { | 
|  | sktext::gpu::STSubRunAllocator<64, 16> arena; | 
|  | exercise(&arena); | 
|  | } | 
|  |  | 
|  | // Exercise arena with a heap allocated starting block | 
|  | { | 
|  | std::unique_ptr<char[]> block{new char[1024]}; | 
|  | SubRunAllocator arena{block.get(), 1024, 0}; | 
|  | exercise(&arena); | 
|  | } | 
|  |  | 
|  | // Exercise the singly-link list of unique_ptrs use case | 
|  | { | 
|  | created = 0; | 
|  | destroyed = 0; | 
|  | SubRunAllocator arena; | 
|  |  | 
|  | struct Node { | 
|  | Node(std::unique_ptr<Node, SubRunAllocator::Destroyer> next) | 
|  | : fNext{std::move(next)} { created++; } | 
|  | ~Node() { destroyed++; } | 
|  | std::unique_ptr<Node, SubRunAllocator::Destroyer> fNext; | 
|  | }; | 
|  |  | 
|  | std::unique_ptr<Node, SubRunAllocator::Destroyer> current = nullptr; | 
|  | for (int i = 0; i < 128; i++) { | 
|  | current = arena.makeUnique<Node>(std::move(current)); | 
|  | } | 
|  | REPORTER_ASSERT(r, created == 128); | 
|  | REPORTER_ASSERT(r, destroyed == 0); | 
|  | } | 
|  | REPORTER_ASSERT(r, created == 128); | 
|  | REPORTER_ASSERT(r, destroyed == 128); | 
|  |  | 
|  | // Exercise the array ctor w/ a mapping function | 
|  | { | 
|  | struct I { | 
|  | I(int v) : i{v} {} | 
|  | ~I() {} | 
|  | int i; | 
|  | }; | 
|  | sktext::gpu::STSubRunAllocator<64, 16> arena; | 
|  | auto a = arena.makeUniqueArray<I>(8, [](size_t i) { return i; }); | 
|  | for (size_t i = 0; i < 8; i++) { | 
|  | REPORTER_ASSERT(r, a[i].i == (int)i); | 
|  | } | 
|  | } | 
|  |  | 
|  | { | 
|  | SubRunAllocator arena(4096); | 
|  | void* ptr = arena.alignedBytes(4081, 8); | 
|  | REPORTER_ASSERT(r, ((intptr_t)ptr & 7) == 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | using TextBlob = sktext::gpu::TextBlob; | 
|  |  | 
|  | DEF_TEST(KeyEqualityOnPerspective, r) { | 
|  | SkTextBlobBuilder builder; | 
|  | SkFont font(ToolUtils::DefaultTypeface(), 16); | 
|  | auto runBuffer = builder.allocRun(font, 1, 0.0f, 0.0f); | 
|  | runBuffer.glyphs[0] = 3; | 
|  | auto blob = builder.make(); | 
|  | sktext::GlyphRunBuilder grBuilder; | 
|  | auto glyphRunList = grBuilder.blobToGlyphRunList(*blob, {100, 100}); | 
|  | SkPaint paint; | 
|  |  | 
|  | // Build the strike device. | 
|  | SkSurfaceProps props; | 
|  | #if !defined(SK_DISABLE_SDF_TEXT) | 
|  | sktext::gpu::SDFTControl control(false, false, false, 1, 100); | 
|  | #else | 
|  | sktext::gpu::SDFTControl control{}; | 
|  | #endif | 
|  | SkStrikeDeviceInfo strikeDevice{props, SkScalerContextFlags::kBoostContrast, &control}; | 
|  | SkMatrix matrix1; | 
|  | matrix1.setAll(1, 0, 0, 0, 1, 0, 1, 1, 1); | 
|  | SkMatrix matrix2; | 
|  | matrix2.setAll(1, 0, 0, 0, 1, 0, 2, 2, 1); | 
|  | auto key1 = std::get<1>( | 
|  | TextBlob::Key::Make(glyphRunList, paint, matrix1, strikeDevice)); | 
|  | auto key2 = std::get<1>( | 
|  | TextBlob::Key::Make(glyphRunList, paint, matrix1, strikeDevice)); | 
|  | auto key3 = std::get<1>( | 
|  | TextBlob::Key::Make(glyphRunList, paint, matrix2, strikeDevice)); | 
|  | REPORTER_ASSERT(r, key1 == key2); | 
|  | REPORTER_ASSERT(r, key1 == key3); | 
|  | } |