blob: bcc7288a2afe800706bc924eea5050786347c747 [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"
20#include "src/core/SkGlyphRun.h"
21#include "tools/fonts/RandomScalerContext.h"
joshualitte49109f2015-07-17 12:47:39 -070022
23#ifdef SK_BUILD_FOR_WIN
Mike Kleinc0bd9f92019-04-23 12:05:21 -050024 #include "include/ports/SkTypeface_win.h"
joshualitte49109f2015-07-17 12:47:39 -070025#endif
26
Mike Kleinc0bd9f92019-04-23 12:05:21 -050027#include "tests/Test.h"
joshualitte49109f2015-07-17 12:47:39 -070028
Mike Kleinc0bd9f92019-04-23 12:05:21 -050029#include "include/gpu/GrContext.h"
30#include "src/gpu/GrContextPriv.h"
joshualitte49109f2015-07-17 12:47:39 -070031
fmalita37283c22016-09-13 10:00:23 -070032static void draw(SkCanvas* canvas, int redraw, const SkTArray<sk_sp<SkTextBlob>>& blobs) {
joshualitt404d9d62015-07-22 11:00:32 -070033 int yOffset = 0;
joshualitte49109f2015-07-17 12:47:39 -070034 for (int r = 0; r < redraw; r++) {
35 for (int i = 0; i < blobs.count(); i++) {
fmalita37283c22016-09-13 10:00:23 -070036 const auto& blob = blobs[i];
joshualitt404d9d62015-07-22 11:00:32 -070037 const SkRect& bounds = blob->bounds();
38 yOffset += SkScalarCeilToInt(bounds.height());
joshualitte49109f2015-07-17 12:47:39 -070039 SkPaint paint;
joshualitt404d9d62015-07-22 11:00:32 -070040 canvas->drawTextBlob(blob, 0, SkIntToScalar(yOffset), paint);
joshualitte49109f2015-07-17 12:47:39 -070041 }
42 }
43}
44
joshualitt11dfc8e2015-07-23 08:30:25 -070045static const int kWidth = 1024;
46static const int kHeight = 768;
joshualitte49109f2015-07-17 12:47:39 -070047
Herb Derbyd3895d82018-09-04 13:27:00 -040048static void setup_always_evict_atlas(GrContext* context) {
Brian Salomon2638f3d2019-10-22 12:20:37 -040049 context->priv().getAtlasManager()->setAtlasDimensionsToMinimum_ForTesting();
Herb Derbyd3895d82018-09-04 13:27:00 -040050}
51
joshualitte49109f2015-07-17 12:47:39 -070052// This test hammers the GPU textblobcache and font atlas
kkinnunen15302832015-12-01 04:35:26 -080053static void text_blob_cache_inner(skiatest::Reporter* reporter, GrContext* context,
joshualitt7f9c9eb2015-08-21 11:08:00 -070054 int maxTotalText, int maxGlyphID, int maxFamilies, bool normal,
55 bool stressTest) {
joshualitte49109f2015-07-17 12:47:39 -070056 // setup surface
57 uint32_t flags = 0;
58 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
59
joshualitt7f9c9eb2015-08-21 11:08:00 -070060 // configure our context for maximum stressing of cache and atlas
61 if (stressTest) {
Herb Derbyd3895d82018-09-04 13:27:00 -040062 setup_always_evict_atlas(context);
Robert Phillipsdbaf3172019-02-06 15:12:53 -050063 context->priv().testingOnly_setTextBlobCacheLimit(0);
joshualitt7f9c9eb2015-08-21 11:08:00 -070064 }
65
Brian Osman7c597742019-03-26 11:10:11 -040066 SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType,
67 kPremul_SkAlphaType);
reede8f30622016-03-23 18:59:25 -070068 auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, &props));
joshualitte49109f2015-07-17 12:47:39 -070069 REPORTER_ASSERT(reporter, surface);
70 if (!surface) {
71 return;
72 }
73
74 SkCanvas* canvas = surface->getCanvas();
75
Hal Canary342b7ac2016-11-04 11:49:42 -040076 sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
joshualitte49109f2015-07-17 12:47:39 -070077
joshualitt65e96b42015-07-31 11:45:22 -070078 int count = SkMin32(fm->countFamilies(), maxFamilies);
joshualitte49109f2015-07-17 12:47:39 -070079
80 // make a ton of text
joshualitt65e96b42015-07-31 11:45:22 -070081 SkAutoTArray<uint16_t> text(maxTotalText);
82 for (int i = 0; i < maxTotalText; i++) {
83 text[i] = i % maxGlyphID;
joshualitte49109f2015-07-17 12:47:39 -070084 }
85
86 // generate textblobs
fmalita37283c22016-09-13 10:00:23 -070087 SkTArray<sk_sp<SkTextBlob>> blobs;
joshualitte49109f2015-07-17 12:47:39 -070088 for (int i = 0; i < count; i++) {
Mike Reed70914f52018-11-23 13:08:33 -050089 SkFont font;
90 font.setSize(48); // draw big glyphs to really stress the atlas
joshualitte49109f2015-07-17 12:47:39 -070091
92 SkString familyName;
93 fm->getFamilyName(i, &familyName);
Hal Canary342b7ac2016-11-04 11:49:42 -040094 sk_sp<SkFontStyleSet> set(fm->createStyleSet(i));
joshualitte49109f2015-07-17 12:47:39 -070095 for (int j = 0; j < set->count(); ++j) {
96 SkFontStyle fs;
halcanary96fcdcc2015-08-27 07:41:13 -070097 set->getStyle(j, &fs, nullptr);
joshualitte49109f2015-07-17 12:47:39 -070098
joshualitt65e96b42015-07-31 11:45:22 -070099 // We use a typeface which randomy returns unexpected mask formats to fuzz
bungeman13b9c952016-05-12 10:09:30 -0700100 sk_sp<SkTypeface> orig(set->createTypeface(j));
joshualitt65e96b42015-07-31 11:45:22 -0700101 if (normal) {
Mike Reed70914f52018-11-23 13:08:33 -0500102 font.setTypeface(orig);
joshualitt65e96b42015-07-31 11:45:22 -0700103 } else {
Mike Reed70914f52018-11-23 13:08:33 -0500104 font.setTypeface(sk_make_sp<SkRandomTypeface>(orig, SkPaint(), true));
joshualitt65e96b42015-07-31 11:45:22 -0700105 }
joshualitte49109f2015-07-17 12:47:39 -0700106
107 SkTextBlobBuilder builder;
108 for (int aa = 0; aa < 2; aa++) {
109 for (int subpixel = 0; subpixel < 2; subpixel++) {
110 for (int lcd = 0; lcd < 2; lcd++) {
Mike Reed70914f52018-11-23 13:08:33 -0500111 font.setEdging(SkFont::Edging::kAlias);
112 if (aa) {
113 font.setEdging(SkFont::Edging::kAntiAlias);
114 if (lcd) {
115 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
116 }
joshualitt65e96b42015-07-31 11:45:22 -0700117 }
Mike Reed70914f52018-11-23 13:08:33 -0500118 font.setSubpixel(SkToBool(subpixel));
119 if (!SkToBool(lcd)) {
120 font.setSize(160);
121 }
122 const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font,
joshualitt65e96b42015-07-31 11:45:22 -0700123 maxTotalText,
joshualitte49109f2015-07-17 12:47:39 -0700124 0, 0,
halcanary96fcdcc2015-08-27 07:41:13 -0700125 nullptr);
joshualitt65e96b42015-07-31 11:45:22 -0700126 memcpy(run.glyphs, text.get(), maxTotalText * sizeof(uint16_t));
joshualitte49109f2015-07-17 12:47:39 -0700127 }
128 }
129 }
fmalita37283c22016-09-13 10:00:23 -0700130 blobs.emplace_back(builder.make());
joshualitte49109f2015-07-17 12:47:39 -0700131 }
132 }
133
joshualitt11dfc8e2015-07-23 08:30:25 -0700134 // create surface where LCD is impossible
Brian Osman7c597742019-03-26 11:10:11 -0400135 info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
joshualitt11dfc8e2015-07-23 08:30:25 -0700136 SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry);
reede8f30622016-03-23 18:59:25 -0700137 auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD));
joshualitt11dfc8e2015-07-23 08:30:25 -0700138 REPORTER_ASSERT(reporter, surface);
139 if (!surface) {
140 return;
141 }
142
143 SkCanvas* canvasNoLCD = surfaceNoLCD->getCanvas();
144
joshualitte49109f2015-07-17 12:47:39 -0700145 // test redraw
146 draw(canvas, 2, blobs);
joshualitt11dfc8e2015-07-23 08:30:25 -0700147 draw(canvasNoLCD, 2, blobs);
joshualitte49109f2015-07-17 12:47:39 -0700148
149 // test draw after free
kkinnunen15302832015-12-01 04:35:26 -0800150 context->freeGpuResources();
joshualitte49109f2015-07-17 12:47:39 -0700151 draw(canvas, 1, blobs);
152
kkinnunen15302832015-12-01 04:35:26 -0800153 context->freeGpuResources();
joshualitt11dfc8e2015-07-23 08:30:25 -0700154 draw(canvasNoLCD, 1, blobs);
155
joshualitte49109f2015-07-17 12:47:39 -0700156 // test draw after abandon
kkinnunen15302832015-12-01 04:35:26 -0800157 context->abandonContext();
joshualitte49109f2015-07-17 12:47:39 -0700158 draw(canvas, 1, blobs);
159}
joshualitt65e96b42015-07-31 11:45:22 -0700160
Brian Osman7c597742019-03-26 11:10:11 -0400161DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
bsalomon8b7451a2016-05-11 06:33:06 -0700162 text_blob_cache_inner(reporter, ctxInfo.grContext(), 1024, 256, 30, true, false);
joshualitt7f9c9eb2015-08-21 11:08:00 -0700163}
164
Brian Osman7c597742019-03-26 11:10:11 -0400165DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
Herb Derby278b0672018-10-05 13:46:51 -0400166 text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, true, true);
joshualitt65e96b42015-07-31 11:45:22 -0700167}
168
Brian Osman7c597742019-03-26 11:10:11 -0400169DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
bsalomon8b7451a2016-05-11 06:33:06 -0700170 text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, false, false);
joshualitt7f9c9eb2015-08-21 11:08:00 -0700171}
172
Brian Osman7c597742019-03-26 11:10:11 -0400173DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
Herb Derby278b0672018-10-05 13:46:51 -0400174 text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, false, true);
joshualitt65e96b42015-07-31 11:45:22 -0700175}
Herb Derby9830fc42019-09-12 10:58:26 -0400176
177static const int kScreenDim = 160;
178
179static SkBitmap draw_blob(SkTextBlob* blob, SkSurface* surface, SkPoint offset) {
180
181 SkPaint paint;
182
183 SkCanvas* canvas = surface->getCanvas();
184 canvas->save();
185 canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
186 canvas->translate(offset.fX, offset.fY);
187 canvas->drawTextBlob(blob, 0, 0, paint);
188 SkBitmap bitmap;
189 bitmap.allocN32Pixels(kScreenDim, kScreenDim);
190 surface->readPixels(bitmap, 0, 0);
191 canvas->restore();
192 return bitmap;
193}
194
195static bool compare_bitmaps(const SkBitmap& expected, const SkBitmap& actual) {
196 SkASSERT(expected.width() == actual.width());
197 SkASSERT(expected.height() == actual.height());
198 for (int i = 0; i < expected.width(); ++i) {
199 for (int j = 0; j < expected.height(); ++j) {
200 SkColor expectedColor = expected.getColor(i, j);
201 SkColor actualColor = actual.getColor(i, j);
202 if (expectedColor != actualColor) {
203 return false;
204 }
205 }
206 }
207 return true;
208}
209
210static sk_sp<SkTextBlob> make_blob() {
211 auto tf = SkTypeface::MakeFromName("Roboto2-Regular", SkFontStyle());
212 SkFont font;
213 font.setTypeface(tf);
214 font.setSubpixel(false);
215 font.setEdging(SkFont::Edging::kAlias);
216 font.setSize(24);
217
218 static char text[] = "HekpqB";
219 static const int maxGlyphLen = sizeof(text) * 4;
220 SkGlyphID glyphs[maxGlyphLen];
221 int glyphCount =
222 font.textToGlyphs(text, sizeof(text), SkTextEncoding::kUTF8, glyphs, maxGlyphLen);
223
224 SkTextBlobBuilder builder;
225 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
226 for (int i = 0; i < glyphCount; i++) {
227 runBuffer.glyphs[i] = glyphs[i];
228 }
229 return builder.make();
230}
231
232static const bool kDumpPngs = true;
233// dump pngs needs a "good" and a "bad" directory to put the results in. This allows the use of the
234// skdiff tool to visualize the differences.
235
236void write_png(const std::string& filename, const SkBitmap& bitmap) {
237 auto data = SkEncodeBitmap(bitmap, SkEncodedImageFormat::kPNG, 0);
238 SkFILEWStream w{filename.c_str()};
239 w.write(data->data(), data->size());
240 w.fsync();
241}
242
243DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph, reporter, ctxInfo) {
244 auto grContext = ctxInfo.grContext();
245 const SkImageInfo info =
246 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
247 auto surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info);
248
249 auto blob = make_blob();
250
251 for (int y = 40; y < kScreenDim - 40; y++) {
252 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
253 SkBitmap half = draw_blob(blob.get(), surface.get(), {40, y + 0.5f});
254 SkBitmap unit = draw_blob(blob.get(), surface.get(), {40, y + 1.0f});
255 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
256 REPORTER_ASSERT(reporter, isOk);
257 if (!isOk) {
258 if (kDumpPngs) {
259 {
260 std::string filename = "bad/half-y" + std::to_string(y) + ".png";
261 write_png(filename, half);
262 }
263 {
264 std::string filename = "good/half-y" + std::to_string(y) + ".png";
265 write_png(filename, base);
266 }
267 }
268 break;
269 }
270 }
271
272 // Testing the x direction across all platforms does not workout, because letter spacing can
273 // change based on non-integer advance widths, but this has been useful for diagnosing problems.
274#if 0
275 blob = make_blob();
276 for (int x = 40; x < kScreenDim - 40; x++) {
277 SkBitmap base = draw_blob(blob.get(), surface.get(), {x + 0.0f, 40});
278 SkBitmap half = draw_blob(blob.get(), surface.get(), {x + 0.5f, 40});
279 SkBitmap unit = draw_blob(blob.get(), surface.get(), {x + 1.0f, 40});
280 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
281 REPORTER_ASSERT(reporter, isOk);
282 if (!isOk) {
283 if (kDumpPngs) {
284 {
285 std::string filename = "bad/half-x" + std::to_string(x) + ".png";
286 write_png(filename, half);
287 }
288 {
289 std::string filename = "good/half-x" + std::to_string(x) + ".png";
290 write_png(filename, base);
291 }
292 }
293 break;
294 }
295 }
296#endif
297}
298
299DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll, reporter, ctxInfo) {
300 auto grContext = ctxInfo.grContext();
301 const SkImageInfo info =
302 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
303 auto surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info);
304
305 auto movingBlob = make_blob();
306
307 for (SkScalar y = 40; y < 50; y += 1.0/8.0) {
308 auto expectedBlob = make_blob();
309 auto expectedBitMap = draw_blob(expectedBlob.get(), surface.get(), {40, y});
310 auto movingBitmap = draw_blob(movingBlob.get(), surface.get(), {40, y});
311 bool isOk = compare_bitmaps(expectedBitMap, movingBitmap);
312 REPORTER_ASSERT(reporter, isOk);
313 if (!isOk) {
314 if (kDumpPngs) {
315 {
316 std::string filename = "bad/scroll-y" + std::to_string(y) + ".png";
317 write_png(filename, movingBitmap);
318 }
319 {
320 std::string filename = "good/scroll-y" + std::to_string(y) + ".png";
321 write_png(filename, expectedBitMap);
322 }
323 }
324 break;
325 }
326 }
327}