blob: 6e72d9e64ac81990116b084fd96c6c2f3bebc574 [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"
22#include "tools/fonts/RandomScalerContext.h"
joshualitte49109f2015-07-17 12:47:39 -070023
24#ifdef SK_BUILD_FOR_WIN
Mike Kleinc0bd9f92019-04-23 12:05:21 -050025 #include "include/ports/SkTypeface_win.h"
joshualitte49109f2015-07-17 12:47:39 -070026#endif
27
Mike Kleinc0bd9f92019-04-23 12:05:21 -050028#include "tests/Test.h"
joshualitte49109f2015-07-17 12:47:39 -070029
Mike Kleinc0bd9f92019-04-23 12:05:21 -050030#include "include/gpu/GrContext.h"
31#include "src/gpu/GrContextPriv.h"
Robert Phillipse19babf2020-04-06 13:57:30 -040032#include "src/gpu/text/GrAtlasManager.h"
Herb Derby1ca54d42020-06-26 12:50:38 -040033#include "src/gpu/text/GrTextBlobCache.h"
joshualitte49109f2015-07-17 12:47:39 -070034
fmalita37283c22016-09-13 10:00:23 -070035static void draw(SkCanvas* canvas, int redraw, const SkTArray<sk_sp<SkTextBlob>>& blobs) {
joshualitt404d9d62015-07-22 11:00:32 -070036 int yOffset = 0;
joshualitte49109f2015-07-17 12:47:39 -070037 for (int r = 0; r < redraw; r++) {
38 for (int i = 0; i < blobs.count(); i++) {
fmalita37283c22016-09-13 10:00:23 -070039 const auto& blob = blobs[i];
joshualitt404d9d62015-07-22 11:00:32 -070040 const SkRect& bounds = blob->bounds();
41 yOffset += SkScalarCeilToInt(bounds.height());
joshualitte49109f2015-07-17 12:47:39 -070042 SkPaint paint;
joshualitt404d9d62015-07-22 11:00:32 -070043 canvas->drawTextBlob(blob, 0, SkIntToScalar(yOffset), paint);
joshualitte49109f2015-07-17 12:47:39 -070044 }
45 }
46}
47
joshualitt11dfc8e2015-07-23 08:30:25 -070048static const int kWidth = 1024;
49static const int kHeight = 768;
joshualitte49109f2015-07-17 12:47:39 -070050
Herb Derbyd3895d82018-09-04 13:27:00 -040051static void setup_always_evict_atlas(GrContext* context) {
Brian Salomon2638f3d2019-10-22 12:20:37 -040052 context->priv().getAtlasManager()->setAtlasDimensionsToMinimum_ForTesting();
Herb Derbyd3895d82018-09-04 13:27:00 -040053}
54
Herb Derby1ca54d42020-06-26 12:50:38 -040055class GrTextBlobTestingPeer {
56public:
57 static void SetBudget(GrTextBlobCache* cache, size_t budget) {
58 SkAutoMutexExclusive lock{cache->fMutex};
59 cache->fSizeBudget = budget;
60 cache->internalCheckPurge();
61 }
62};
63
joshualitte49109f2015-07-17 12:47:39 -070064// This test hammers the GPU textblobcache and font atlas
kkinnunen15302832015-12-01 04:35:26 -080065static void text_blob_cache_inner(skiatest::Reporter* reporter, GrContext* context,
joshualitt7f9c9eb2015-08-21 11:08:00 -070066 int maxTotalText, int maxGlyphID, int maxFamilies, bool normal,
67 bool stressTest) {
joshualitte49109f2015-07-17 12:47:39 -070068 // setup surface
69 uint32_t flags = 0;
70 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
71
joshualitt7f9c9eb2015-08-21 11:08:00 -070072 // configure our context for maximum stressing of cache and atlas
73 if (stressTest) {
Herb Derbyd3895d82018-09-04 13:27:00 -040074 setup_always_evict_atlas(context);
Herb Derby1ca54d42020-06-26 12:50:38 -040075 GrTextBlobTestingPeer::SetBudget(context->priv().getTextBlobCache(), 0);
joshualitt7f9c9eb2015-08-21 11:08:00 -070076 }
77
Brian Osman7c597742019-03-26 11:10:11 -040078 SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType,
79 kPremul_SkAlphaType);
reede8f30622016-03-23 18:59:25 -070080 auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, &props));
joshualitte49109f2015-07-17 12:47:39 -070081 REPORTER_ASSERT(reporter, surface);
82 if (!surface) {
83 return;
84 }
85
86 SkCanvas* canvas = surface->getCanvas();
87
Hal Canary342b7ac2016-11-04 11:49:42 -040088 sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
joshualitte49109f2015-07-17 12:47:39 -070089
Brian Osman7f364052020-02-06 11:25:43 -050090 int count = std::min(fm->countFamilies(), maxFamilies);
joshualitte49109f2015-07-17 12:47:39 -070091
92 // make a ton of text
joshualitt65e96b42015-07-31 11:45:22 -070093 SkAutoTArray<uint16_t> text(maxTotalText);
94 for (int i = 0; i < maxTotalText; i++) {
95 text[i] = i % maxGlyphID;
joshualitte49109f2015-07-17 12:47:39 -070096 }
97
98 // generate textblobs
fmalita37283c22016-09-13 10:00:23 -070099 SkTArray<sk_sp<SkTextBlob>> blobs;
joshualitte49109f2015-07-17 12:47:39 -0700100 for (int i = 0; i < count; i++) {
Mike Reed70914f52018-11-23 13:08:33 -0500101 SkFont font;
102 font.setSize(48); // draw big glyphs to really stress the atlas
joshualitte49109f2015-07-17 12:47:39 -0700103
104 SkString familyName;
105 fm->getFamilyName(i, &familyName);
Hal Canary342b7ac2016-11-04 11:49:42 -0400106 sk_sp<SkFontStyleSet> set(fm->createStyleSet(i));
joshualitte49109f2015-07-17 12:47:39 -0700107 for (int j = 0; j < set->count(); ++j) {
108 SkFontStyle fs;
halcanary96fcdcc2015-08-27 07:41:13 -0700109 set->getStyle(j, &fs, nullptr);
joshualitte49109f2015-07-17 12:47:39 -0700110
joshualitt65e96b42015-07-31 11:45:22 -0700111 // We use a typeface which randomy returns unexpected mask formats to fuzz
bungeman13b9c952016-05-12 10:09:30 -0700112 sk_sp<SkTypeface> orig(set->createTypeface(j));
joshualitt65e96b42015-07-31 11:45:22 -0700113 if (normal) {
Mike Reed70914f52018-11-23 13:08:33 -0500114 font.setTypeface(orig);
joshualitt65e96b42015-07-31 11:45:22 -0700115 } else {
Mike Reed70914f52018-11-23 13:08:33 -0500116 font.setTypeface(sk_make_sp<SkRandomTypeface>(orig, SkPaint(), true));
joshualitt65e96b42015-07-31 11:45:22 -0700117 }
joshualitte49109f2015-07-17 12:47:39 -0700118
119 SkTextBlobBuilder builder;
120 for (int aa = 0; aa < 2; aa++) {
121 for (int subpixel = 0; subpixel < 2; subpixel++) {
122 for (int lcd = 0; lcd < 2; lcd++) {
Mike Reed70914f52018-11-23 13:08:33 -0500123 font.setEdging(SkFont::Edging::kAlias);
124 if (aa) {
125 font.setEdging(SkFont::Edging::kAntiAlias);
126 if (lcd) {
127 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
128 }
joshualitt65e96b42015-07-31 11:45:22 -0700129 }
Mike Reed70914f52018-11-23 13:08:33 -0500130 font.setSubpixel(SkToBool(subpixel));
131 if (!SkToBool(lcd)) {
132 font.setSize(160);
133 }
134 const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font,
joshualitt65e96b42015-07-31 11:45:22 -0700135 maxTotalText,
joshualitte49109f2015-07-17 12:47:39 -0700136 0, 0,
halcanary96fcdcc2015-08-27 07:41:13 -0700137 nullptr);
joshualitt65e96b42015-07-31 11:45:22 -0700138 memcpy(run.glyphs, text.get(), maxTotalText * sizeof(uint16_t));
joshualitte49109f2015-07-17 12:47:39 -0700139 }
140 }
141 }
fmalita37283c22016-09-13 10:00:23 -0700142 blobs.emplace_back(builder.make());
joshualitte49109f2015-07-17 12:47:39 -0700143 }
144 }
145
joshualitt11dfc8e2015-07-23 08:30:25 -0700146 // create surface where LCD is impossible
Brian Osman7c597742019-03-26 11:10:11 -0400147 info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
joshualitt11dfc8e2015-07-23 08:30:25 -0700148 SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry);
reede8f30622016-03-23 18:59:25 -0700149 auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD));
joshualitt11dfc8e2015-07-23 08:30:25 -0700150 REPORTER_ASSERT(reporter, surface);
151 if (!surface) {
152 return;
153 }
154
155 SkCanvas* canvasNoLCD = surfaceNoLCD->getCanvas();
156
joshualitte49109f2015-07-17 12:47:39 -0700157 // test redraw
158 draw(canvas, 2, blobs);
joshualitt11dfc8e2015-07-23 08:30:25 -0700159 draw(canvasNoLCD, 2, blobs);
joshualitte49109f2015-07-17 12:47:39 -0700160
161 // test draw after free
kkinnunen15302832015-12-01 04:35:26 -0800162 context->freeGpuResources();
joshualitte49109f2015-07-17 12:47:39 -0700163 draw(canvas, 1, blobs);
164
kkinnunen15302832015-12-01 04:35:26 -0800165 context->freeGpuResources();
joshualitt11dfc8e2015-07-23 08:30:25 -0700166 draw(canvasNoLCD, 1, blobs);
167
joshualitte49109f2015-07-17 12:47:39 -0700168 // test draw after abandon
kkinnunen15302832015-12-01 04:35:26 -0800169 context->abandonContext();
joshualitte49109f2015-07-17 12:47:39 -0700170 draw(canvas, 1, blobs);
171}
joshualitt65e96b42015-07-31 11:45:22 -0700172
Brian Osman7c597742019-03-26 11:10:11 -0400173DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400174 text_blob_cache_inner(reporter, ctxInfo.directContext(), 1024, 256, 30, true, false);
joshualitt7f9c9eb2015-08-21 11:08:00 -0700175}
176
Brian Osman7c597742019-03-26 11:10:11 -0400177DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400178 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, true, true);
joshualitt65e96b42015-07-31 11:45:22 -0700179}
180
Brian Osman7c597742019-03-26 11:10:11 -0400181DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400182 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, false);
joshualitt7f9c9eb2015-08-21 11:08:00 -0700183}
184
Brian Osman7c597742019-03-26 11:10:11 -0400185DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400186 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, true);
joshualitt65e96b42015-07-31 11:45:22 -0700187}
Herb Derby9830fc42019-09-12 10:58:26 -0400188
189static const int kScreenDim = 160;
190
191static SkBitmap draw_blob(SkTextBlob* blob, SkSurface* surface, SkPoint offset) {
192
193 SkPaint paint;
194
195 SkCanvas* canvas = surface->getCanvas();
196 canvas->save();
197 canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
198 canvas->translate(offset.fX, offset.fY);
199 canvas->drawTextBlob(blob, 0, 0, paint);
200 SkBitmap bitmap;
201 bitmap.allocN32Pixels(kScreenDim, kScreenDim);
202 surface->readPixels(bitmap, 0, 0);
203 canvas->restore();
204 return bitmap;
205}
206
207static bool compare_bitmaps(const SkBitmap& expected, const SkBitmap& actual) {
208 SkASSERT(expected.width() == actual.width());
209 SkASSERT(expected.height() == actual.height());
210 for (int i = 0; i < expected.width(); ++i) {
211 for (int j = 0; j < expected.height(); ++j) {
212 SkColor expectedColor = expected.getColor(i, j);
213 SkColor actualColor = actual.getColor(i, j);
214 if (expectedColor != actualColor) {
215 return false;
216 }
217 }
218 }
219 return true;
220}
221
222static sk_sp<SkTextBlob> make_blob() {
223 auto tf = SkTypeface::MakeFromName("Roboto2-Regular", SkFontStyle());
224 SkFont font;
225 font.setTypeface(tf);
226 font.setSubpixel(false);
227 font.setEdging(SkFont::Edging::kAlias);
228 font.setSize(24);
229
230 static char text[] = "HekpqB";
231 static const int maxGlyphLen = sizeof(text) * 4;
232 SkGlyphID glyphs[maxGlyphLen];
233 int glyphCount =
234 font.textToGlyphs(text, sizeof(text), SkTextEncoding::kUTF8, glyphs, maxGlyphLen);
235
236 SkTextBlobBuilder builder;
237 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
238 for (int i = 0; i < glyphCount; i++) {
239 runBuffer.glyphs[i] = glyphs[i];
240 }
241 return builder.make();
242}
243
Herb Derby88a3b462020-05-22 11:35:02 -0400244// Turned off to pass on android and ios devices, which were running out of memory..
245#if 0
Herb Derby317dce52020-05-21 14:46:06 -0400246static sk_sp<SkTextBlob> make_large_blob() {
247 auto tf = SkTypeface::MakeFromName("Roboto2-Regular", SkFontStyle());
248 SkFont font;
249 font.setTypeface(tf);
250 font.setSubpixel(false);
251 font.setEdging(SkFont::Edging::kAlias);
252 font.setSize(24);
253
254 const int mallocSize = 0x3c3c3bd; // x86 size
255 std::unique_ptr<char[]> text{new char[mallocSize + 1]};
256 if (text == nullptr) {
257 return nullptr;
258 }
259 for (int i = 0; i < mallocSize; i++) {
260 text[i] = 'x';
261 }
262 text[mallocSize] = 0;
263
264 static const int maxGlyphLen = mallocSize;
265 std::unique_ptr<SkGlyphID[]> glyphs{new SkGlyphID[maxGlyphLen]};
266 int glyphCount =
267 font.textToGlyphs(
268 text.get(), mallocSize, SkTextEncoding::kUTF8, glyphs.get(), maxGlyphLen);
269 SkTextBlobBuilder builder;
270 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
271 for (int i = 0; i < glyphCount; i++) {
272 runBuffer.glyphs[i] = glyphs[i];
273 }
274 return builder.make();
275}
276
277DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobIntegerOverflowTest, reporter, ctxInfo) {
278 auto grContext = ctxInfo.grContext();
279 const SkImageInfo info =
280 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
281 auto surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info);
282
283 auto blob = make_large_blob();
284 int y = 40;
285 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
286}
Herb Derby88a3b462020-05-22 11:35:02 -0400287#endif
Herb Derby317dce52020-05-21 14:46:06 -0400288
Herb Derby9830fc42019-09-12 10:58:26 -0400289static const bool kDumpPngs = true;
290// dump pngs needs a "good" and a "bad" directory to put the results in. This allows the use of the
291// skdiff tool to visualize the differences.
292
293void write_png(const std::string& filename, const SkBitmap& bitmap) {
294 auto data = SkEncodeBitmap(bitmap, SkEncodedImageFormat::kPNG, 0);
295 SkFILEWStream w{filename.c_str()};
296 w.write(data->data(), data->size());
297 w.fsync();
298}
299
300DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400301 auto direct = ctxInfo.directContext();
Herb Derby9830fc42019-09-12 10:58:26 -0400302 const SkImageInfo info =
303 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
Robert Phillips6d344c32020-07-06 10:56:46 -0400304 auto surface = SkSurface::MakeRenderTarget(direct, SkBudgeted::kNo, info);
Herb Derby9830fc42019-09-12 10:58:26 -0400305
306 auto blob = make_blob();
307
308 for (int y = 40; y < kScreenDim - 40; y++) {
309 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
310 SkBitmap half = draw_blob(blob.get(), surface.get(), {40, y + 0.5f});
311 SkBitmap unit = draw_blob(blob.get(), surface.get(), {40, y + 1.0f});
312 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
313 REPORTER_ASSERT(reporter, isOk);
314 if (!isOk) {
315 if (kDumpPngs) {
316 {
317 std::string filename = "bad/half-y" + std::to_string(y) + ".png";
318 write_png(filename, half);
319 }
320 {
321 std::string filename = "good/half-y" + std::to_string(y) + ".png";
322 write_png(filename, base);
323 }
324 }
325 break;
326 }
327 }
328
329 // Testing the x direction across all platforms does not workout, because letter spacing can
330 // change based on non-integer advance widths, but this has been useful for diagnosing problems.
331#if 0
332 blob = make_blob();
333 for (int x = 40; x < kScreenDim - 40; x++) {
334 SkBitmap base = draw_blob(blob.get(), surface.get(), {x + 0.0f, 40});
335 SkBitmap half = draw_blob(blob.get(), surface.get(), {x + 0.5f, 40});
336 SkBitmap unit = draw_blob(blob.get(), surface.get(), {x + 1.0f, 40});
337 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
338 REPORTER_ASSERT(reporter, isOk);
339 if (!isOk) {
340 if (kDumpPngs) {
341 {
342 std::string filename = "bad/half-x" + std::to_string(x) + ".png";
343 write_png(filename, half);
344 }
345 {
346 std::string filename = "good/half-x" + std::to_string(x) + ".png";
347 write_png(filename, base);
348 }
349 }
350 break;
351 }
352 }
353#endif
354}
355
356DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll, reporter, ctxInfo) {
Robert Phillips6d344c32020-07-06 10:56:46 -0400357 auto direct = ctxInfo.directContext();
Herb Derby9830fc42019-09-12 10:58:26 -0400358 const SkImageInfo info =
359 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
Robert Phillips6d344c32020-07-06 10:56:46 -0400360 auto surface = SkSurface::MakeRenderTarget(direct, SkBudgeted::kNo, info);
Herb Derby9830fc42019-09-12 10:58:26 -0400361
362 auto movingBlob = make_blob();
363
364 for (SkScalar y = 40; y < 50; y += 1.0/8.0) {
365 auto expectedBlob = make_blob();
366 auto expectedBitMap = draw_blob(expectedBlob.get(), surface.get(), {40, y});
367 auto movingBitmap = draw_blob(movingBlob.get(), surface.get(), {40, y});
368 bool isOk = compare_bitmaps(expectedBitMap, movingBitmap);
369 REPORTER_ASSERT(reporter, isOk);
370 if (!isOk) {
371 if (kDumpPngs) {
372 {
373 std::string filename = "bad/scroll-y" + std::to_string(y) + ".png";
374 write_png(filename, movingBitmap);
375 }
376 {
377 std::string filename = "good/scroll-y" + std::to_string(y) + ".png";
378 write_png(filename, expectedBitMap);
379 }
380 }
381 break;
382 }
383 }
384}