blob: 66d716231a051c98051d6e089d72bb66ce60daa1 [file] [log] [blame]
joshualitte49109f2015-07-17 12:47:39 -07001/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "tools/ToolUtils.h"
joshualitt7f9c9eb2015-08-21 11:08:00 -07009
Herb Derby9830fc42019-09-12 10:58:26 -040010#include <string>
11
Mike Kleinc0bd9f92019-04-23 12:05:21 -050012#include "include/core/SkCanvas.h"
13#include "include/core/SkFontMgr.h"
14#include "include/core/SkGraphics.h"
15#include "include/core/SkPaint.h"
16#include "include/core/SkPoint.h"
17#include "include/core/SkSurface.h"
18#include "include/core/SkTextBlob.h"
19#include "include/core/SkTypeface.h"
Robert Phillips6d344c32020-07-06 10:56:46 -040020#include "include/gpu/GrDirectContext.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050021#include "src/core/SkGlyphRun.h"
Robert Phillips0c5bb2f2020-07-17 15:40:13 -040022#include "src/gpu/GrContextPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050023#include "tools/fonts/RandomScalerContext.h"
joshualitte49109f2015-07-17 12:47:39 -070024
25#ifdef SK_BUILD_FOR_WIN
Mike Kleinc0bd9f92019-04-23 12:05:21 -050026 #include "include/ports/SkTypeface_win.h"
joshualitte49109f2015-07-17 12:47:39 -070027#endif
28
Mike Kleinc0bd9f92019-04-23 12:05:21 -050029#include "tests/Test.h"
joshualitte49109f2015-07-17 12:47:39 -070030
Robert Phillipse19babf2020-04-06 13:57:30 -040031#include "src/gpu/text/GrAtlasManager.h"
Herb Derby1ca54d42020-06-26 12:50:38 -040032#include "src/gpu/text/GrTextBlobCache.h"
joshualitte49109f2015-07-17 12:47:39 -070033
fmalita37283c22016-09-13 10:00:23 -070034static void draw(SkCanvas* canvas, int redraw, const SkTArray<sk_sp<SkTextBlob>>& blobs) {
joshualitt404d9d62015-07-22 11:00:32 -070035 int yOffset = 0;
joshualitte49109f2015-07-17 12:47:39 -070036 for (int r = 0; r < redraw; r++) {
37 for (int i = 0; i < blobs.count(); i++) {
fmalita37283c22016-09-13 10:00:23 -070038 const auto& blob = blobs[i];
joshualitt404d9d62015-07-22 11:00:32 -070039 const SkRect& bounds = blob->bounds();
40 yOffset += SkScalarCeilToInt(bounds.height());
joshualitte49109f2015-07-17 12:47:39 -070041 SkPaint paint;
joshualitt404d9d62015-07-22 11:00:32 -070042 canvas->drawTextBlob(blob, 0, SkIntToScalar(yOffset), paint);
joshualitte49109f2015-07-17 12:47:39 -070043 }
44 }
45}
46
joshualitt11dfc8e2015-07-23 08:30:25 -070047static const int kWidth = 1024;
48static const int kHeight = 768;
joshualitte49109f2015-07-17 12:47:39 -070049
Robert Phillips0c5bb2f2020-07-17 15:40:13 -040050static void setup_always_evict_atlas(GrDirectContext* dContext) {
51 dContext->priv().getAtlasManager()->setAtlasDimensionsToMinimum_ForTesting();
Herb Derbyd3895d82018-09-04 13:27:00 -040052}
53
Herb Derby1ca54d42020-06-26 12:50:38 -040054class GrTextBlobTestingPeer {
55public:
56 static void SetBudget(GrTextBlobCache* cache, size_t budget) {
57 SkAutoMutexExclusive lock{cache->fMutex};
58 cache->fSizeBudget = budget;
59 cache->internalCheckPurge();
60 }
61};
62
joshualitte49109f2015-07-17 12:47:39 -070063// This test hammers the GPU textblobcache and font atlas
Robert Phillips0c5bb2f2020-07-17 15:40:13 -040064static void text_blob_cache_inner(skiatest::Reporter* reporter, GrDirectContext* dContext,
joshualitt7f9c9eb2015-08-21 11:08:00 -070065 int maxTotalText, int maxGlyphID, int maxFamilies, bool normal,
66 bool stressTest) {
joshualitte49109f2015-07-17 12:47:39 -070067 // setup surface
68 uint32_t flags = 0;
69 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
70
joshualitt7f9c9eb2015-08-21 11:08:00 -070071 // configure our context for maximum stressing of cache and atlas
72 if (stressTest) {
Robert Phillips0c5bb2f2020-07-17 15:40:13 -040073 setup_always_evict_atlas(dContext);
74 GrTextBlobTestingPeer::SetBudget(dContext->priv().getTextBlobCache(), 0);
joshualitt7f9c9eb2015-08-21 11:08:00 -070075 }
76
Brian Osman7c597742019-03-26 11:10:11 -040077 SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType,
78 kPremul_SkAlphaType);
Robert Phillips0c5bb2f2020-07-17 15:40:13 -040079 auto surface(SkSurface::MakeRenderTarget(dContext, SkBudgeted::kNo, info, 0, &props));
joshualitte49109f2015-07-17 12:47:39 -070080 REPORTER_ASSERT(reporter, surface);
81 if (!surface) {
82 return;
83 }
84
85 SkCanvas* canvas = surface->getCanvas();
86
Hal Canary342b7ac2016-11-04 11:49:42 -040087 sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
joshualitte49109f2015-07-17 12:47:39 -070088
Brian Osman7f364052020-02-06 11:25:43 -050089 int count = std::min(fm->countFamilies(), maxFamilies);
joshualitte49109f2015-07-17 12:47:39 -070090
91 // make a ton of text
joshualitt65e96b42015-07-31 11:45:22 -070092 SkAutoTArray<uint16_t> text(maxTotalText);
93 for (int i = 0; i < maxTotalText; i++) {
94 text[i] = i % maxGlyphID;
joshualitte49109f2015-07-17 12:47:39 -070095 }
96
97 // generate textblobs
fmalita37283c22016-09-13 10:00:23 -070098 SkTArray<sk_sp<SkTextBlob>> blobs;
joshualitte49109f2015-07-17 12:47:39 -070099 for (int i = 0; i < count; i++) {
Mike Reed70914f52018-11-23 13:08:33 -0500100 SkFont font;
101 font.setSize(48); // draw big glyphs to really stress the atlas
joshualitte49109f2015-07-17 12:47:39 -0700102
103 SkString familyName;
104 fm->getFamilyName(i, &familyName);
Hal Canary342b7ac2016-11-04 11:49:42 -0400105 sk_sp<SkFontStyleSet> set(fm->createStyleSet(i));
joshualitte49109f2015-07-17 12:47:39 -0700106 for (int j = 0; j < set->count(); ++j) {
107 SkFontStyle fs;
halcanary96fcdcc2015-08-27 07:41:13 -0700108 set->getStyle(j, &fs, nullptr);
joshualitte49109f2015-07-17 12:47:39 -0700109
joshualitt65e96b42015-07-31 11:45:22 -0700110 // We use a typeface which randomy returns unexpected mask formats to fuzz
bungeman13b9c952016-05-12 10:09:30 -0700111 sk_sp<SkTypeface> orig(set->createTypeface(j));
joshualitt65e96b42015-07-31 11:45:22 -0700112 if (normal) {
Mike Reed70914f52018-11-23 13:08:33 -0500113 font.setTypeface(orig);
joshualitt65e96b42015-07-31 11:45:22 -0700114 } else {
Mike Reed70914f52018-11-23 13:08:33 -0500115 font.setTypeface(sk_make_sp<SkRandomTypeface>(orig, SkPaint(), true));
joshualitt65e96b42015-07-31 11:45:22 -0700116 }
joshualitte49109f2015-07-17 12:47:39 -0700117
118 SkTextBlobBuilder builder;
119 for (int aa = 0; aa < 2; aa++) {
120 for (int subpixel = 0; subpixel < 2; subpixel++) {
121 for (int lcd = 0; lcd < 2; lcd++) {
Mike Reed70914f52018-11-23 13:08:33 -0500122 font.setEdging(SkFont::Edging::kAlias);
123 if (aa) {
124 font.setEdging(SkFont::Edging::kAntiAlias);
125 if (lcd) {
126 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
127 }
joshualitt65e96b42015-07-31 11:45:22 -0700128 }
Mike Reed70914f52018-11-23 13:08:33 -0500129 font.setSubpixel(SkToBool(subpixel));
130 if (!SkToBool(lcd)) {
131 font.setSize(160);
132 }
133 const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font,
joshualitt65e96b42015-07-31 11:45:22 -0700134 maxTotalText,
joshualitte49109f2015-07-17 12:47:39 -0700135 0, 0,
halcanary96fcdcc2015-08-27 07:41:13 -0700136 nullptr);
joshualitt65e96b42015-07-31 11:45:22 -0700137 memcpy(run.glyphs, text.get(), maxTotalText * sizeof(uint16_t));
joshualitte49109f2015-07-17 12:47:39 -0700138 }
139 }
140 }
fmalita37283c22016-09-13 10:00:23 -0700141 blobs.emplace_back(builder.make());
joshualitte49109f2015-07-17 12:47:39 -0700142 }
143 }
144
joshualitt11dfc8e2015-07-23 08:30:25 -0700145 // create surface where LCD is impossible
Brian Osman7c597742019-03-26 11:10:11 -0400146 info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
joshualitt11dfc8e2015-07-23 08:30:25 -0700147 SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry);
reede8f30622016-03-23 18:59:25 -0700148 auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD));
joshualitt11dfc8e2015-07-23 08:30:25 -0700149 REPORTER_ASSERT(reporter, surface);
150 if (!surface) {
151 return;
152 }
153
154 SkCanvas* canvasNoLCD = surfaceNoLCD->getCanvas();
155
joshualitte49109f2015-07-17 12:47:39 -0700156 // test redraw
157 draw(canvas, 2, blobs);
joshualitt11dfc8e2015-07-23 08:30:25 -0700158 draw(canvasNoLCD, 2, blobs);
joshualitte49109f2015-07-17 12:47:39 -0700159
160 // test draw after free
Robert Phillips0c5bb2f2020-07-17 15:40:13 -0400161 dContext->freeGpuResources();
joshualitte49109f2015-07-17 12:47:39 -0700162 draw(canvas, 1, blobs);
163
Robert Phillips0c5bb2f2020-07-17 15:40:13 -0400164 dContext->freeGpuResources();
joshualitt11dfc8e2015-07-23 08:30:25 -0700165 draw(canvasNoLCD, 1, blobs);
166
joshualitte49109f2015-07-17 12:47:39 -0700167 // test draw after abandon
Robert Phillips0c5bb2f2020-07-17 15:40:13 -0400168 dContext->abandonContext();
joshualitte49109f2015-07-17 12:47:39 -0700169 draw(canvas, 1, blobs);
170}
joshualitt65e96b42015-07-31 11:45:22 -0700171
Brian Osman7c597742019-03-26 11:10:11 -0400172DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400173 text_blob_cache_inner(reporter, ctxInfo.directContext(), 1024, 256, 30, true, false);
joshualitt7f9c9eb2015-08-21 11:08:00 -0700174}
175
Brian Osman7c597742019-03-26 11:10:11 -0400176DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400177 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, true, true);
joshualitt65e96b42015-07-31 11:45:22 -0700178}
179
Brian Osman7c597742019-03-26 11:10:11 -0400180DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400181 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, false);
joshualitt7f9c9eb2015-08-21 11:08:00 -0700182}
183
Brian Osman7c597742019-03-26 11:10:11 -0400184DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400185 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, true);
joshualitt65e96b42015-07-31 11:45:22 -0700186}
Herb Derby9830fc42019-09-12 10:58:26 -0400187
188static const int kScreenDim = 160;
189
190static SkBitmap draw_blob(SkTextBlob* blob, SkSurface* surface, SkPoint offset) {
191
192 SkPaint paint;
193
194 SkCanvas* canvas = surface->getCanvas();
195 canvas->save();
196 canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
197 canvas->translate(offset.fX, offset.fY);
198 canvas->drawTextBlob(blob, 0, 0, paint);
199 SkBitmap bitmap;
200 bitmap.allocN32Pixels(kScreenDim, kScreenDim);
201 surface->readPixels(bitmap, 0, 0);
202 canvas->restore();
203 return bitmap;
204}
205
206static bool compare_bitmaps(const SkBitmap& expected, const SkBitmap& actual) {
207 SkASSERT(expected.width() == actual.width());
208 SkASSERT(expected.height() == actual.height());
209 for (int i = 0; i < expected.width(); ++i) {
210 for (int j = 0; j < expected.height(); ++j) {
211 SkColor expectedColor = expected.getColor(i, j);
212 SkColor actualColor = actual.getColor(i, j);
213 if (expectedColor != actualColor) {
214 return false;
215 }
216 }
217 }
218 return true;
219}
220
221static sk_sp<SkTextBlob> make_blob() {
222 auto tf = SkTypeface::MakeFromName("Roboto2-Regular", SkFontStyle());
223 SkFont font;
224 font.setTypeface(tf);
225 font.setSubpixel(false);
226 font.setEdging(SkFont::Edging::kAlias);
227 font.setSize(24);
228
229 static char text[] = "HekpqB";
230 static const int maxGlyphLen = sizeof(text) * 4;
231 SkGlyphID glyphs[maxGlyphLen];
232 int glyphCount =
233 font.textToGlyphs(text, sizeof(text), SkTextEncoding::kUTF8, glyphs, maxGlyphLen);
234
235 SkTextBlobBuilder builder;
236 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
237 for (int i = 0; i < glyphCount; i++) {
238 runBuffer.glyphs[i] = glyphs[i];
239 }
240 return builder.make();
241}
242
Herb Derby88a3b462020-05-22 11:35:02 -0400243// Turned off to pass on android and ios devices, which were running out of memory..
244#if 0
Herb Derby317dce52020-05-21 14:46:06 -0400245static sk_sp<SkTextBlob> make_large_blob() {
246 auto tf = SkTypeface::MakeFromName("Roboto2-Regular", SkFontStyle());
247 SkFont font;
248 font.setTypeface(tf);
249 font.setSubpixel(false);
250 font.setEdging(SkFont::Edging::kAlias);
251 font.setSize(24);
252
253 const int mallocSize = 0x3c3c3bd; // x86 size
254 std::unique_ptr<char[]> text{new char[mallocSize + 1]};
255 if (text == nullptr) {
256 return nullptr;
257 }
258 for (int i = 0; i < mallocSize; i++) {
259 text[i] = 'x';
260 }
261 text[mallocSize] = 0;
262
263 static const int maxGlyphLen = mallocSize;
264 std::unique_ptr<SkGlyphID[]> glyphs{new SkGlyphID[maxGlyphLen]};
265 int glyphCount =
266 font.textToGlyphs(
267 text.get(), mallocSize, SkTextEncoding::kUTF8, glyphs.get(), maxGlyphLen);
268 SkTextBlobBuilder builder;
269 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
270 for (int i = 0; i < glyphCount; i++) {
271 runBuffer.glyphs[i] = glyphs[i];
272 }
273 return builder.make();
274}
275
276DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobIntegerOverflowTest, reporter, ctxInfo) {
Robert Phillips0c5bb2f2020-07-17 15:40:13 -0400277 auto dContext = ctxInfo.directContext();
Herb Derby317dce52020-05-21 14:46:06 -0400278 const SkImageInfo info =
279 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
Robert Phillips0c5bb2f2020-07-17 15:40:13 -0400280 auto surface = SkSurface::MakeRenderTarget(dContext, SkBudgeted::kNo, info);
Herb Derby317dce52020-05-21 14:46:06 -0400281
282 auto blob = make_large_blob();
283 int y = 40;
284 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
285}
Herb Derby88a3b462020-05-22 11:35:02 -0400286#endif
Herb Derby317dce52020-05-21 14:46:06 -0400287
Herb Derby9830fc42019-09-12 10:58:26 -0400288static const bool kDumpPngs = true;
289// dump pngs needs a "good" and a "bad" directory to put the results in. This allows the use of the
290// skdiff tool to visualize the differences.
291
292void write_png(const std::string& filename, const SkBitmap& bitmap) {
293 auto data = SkEncodeBitmap(bitmap, SkEncodedImageFormat::kPNG, 0);
294 SkFILEWStream w{filename.c_str()};
295 w.write(data->data(), data->size());
296 w.fsync();
297}
298
299DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400300 auto direct = ctxInfo.directContext();
Herb Derby9830fc42019-09-12 10:58:26 -0400301 const SkImageInfo info =
302 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
Robert Phillips6d344c32020-07-06 10:56:46 -0400303 auto surface = SkSurface::MakeRenderTarget(direct, SkBudgeted::kNo, info);
Herb Derby9830fc42019-09-12 10:58:26 -0400304
305 auto blob = make_blob();
306
307 for (int y = 40; y < kScreenDim - 40; y++) {
308 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
309 SkBitmap half = draw_blob(blob.get(), surface.get(), {40, y + 0.5f});
310 SkBitmap unit = draw_blob(blob.get(), surface.get(), {40, y + 1.0f});
311 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
312 REPORTER_ASSERT(reporter, isOk);
313 if (!isOk) {
314 if (kDumpPngs) {
315 {
316 std::string filename = "bad/half-y" + std::to_string(y) + ".png";
317 write_png(filename, half);
318 }
319 {
320 std::string filename = "good/half-y" + std::to_string(y) + ".png";
321 write_png(filename, base);
322 }
323 }
324 break;
325 }
326 }
327
328 // Testing the x direction across all platforms does not workout, because letter spacing can
329 // change based on non-integer advance widths, but this has been useful for diagnosing problems.
330#if 0
331 blob = make_blob();
332 for (int x = 40; x < kScreenDim - 40; x++) {
333 SkBitmap base = draw_blob(blob.get(), surface.get(), {x + 0.0f, 40});
334 SkBitmap half = draw_blob(blob.get(), surface.get(), {x + 0.5f, 40});
335 SkBitmap unit = draw_blob(blob.get(), surface.get(), {x + 1.0f, 40});
336 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
337 REPORTER_ASSERT(reporter, isOk);
338 if (!isOk) {
339 if (kDumpPngs) {
340 {
341 std::string filename = "bad/half-x" + std::to_string(x) + ".png";
342 write_png(filename, half);
343 }
344 {
345 std::string filename = "good/half-x" + std::to_string(x) + ".png";
346 write_png(filename, base);
347 }
348 }
349 break;
350 }
351 }
352#endif
353}
354
355DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400356 auto direct = ctxInfo.directContext();
Herb Derby9830fc42019-09-12 10:58:26 -0400357 const SkImageInfo info =
358 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
Robert Phillips6d344c32020-07-06 10:56:46 -0400359 auto surface = SkSurface::MakeRenderTarget(direct, SkBudgeted::kNo, info);
Herb Derby9830fc42019-09-12 10:58:26 -0400360
361 auto movingBlob = make_blob();
362
363 for (SkScalar y = 40; y < 50; y += 1.0/8.0) {
364 auto expectedBlob = make_blob();
365 auto expectedBitMap = draw_blob(expectedBlob.get(), surface.get(), {40, y});
366 auto movingBitmap = draw_blob(movingBlob.get(), surface.get(), {40, y});
367 bool isOk = compare_bitmaps(expectedBitMap, movingBitmap);
368 REPORTER_ASSERT(reporter, isOk);
369 if (!isOk) {
370 if (kDumpPngs) {
371 {
372 std::string filename = "bad/scroll-y" + std::to_string(y) + ".png";
373 write_png(filename, movingBitmap);
374 }
375 {
376 std::string filename = "good/scroll-y" + std::to_string(y) + ".png";
377 write_png(filename, expectedBitMap);
378 }
379 }
380 break;
381 }
382 }
383}