1 /*
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
8 #include "include/core/SkAlphaType.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkColorType.h"
14 #include "include/core/SkDataTable.h"
15 #include "include/core/SkFont.h"
16 #include "include/core/SkFontMgr.h"
17 #include "include/core/SkFontStyle.h"
18 #include "include/core/SkFontTypes.h"
19 #include "include/core/SkImageInfo.h"
20 #include "include/core/SkPaint.h"
21 #include "include/core/SkPoint.h"
22 #include "include/core/SkRect.h"
23 #include "include/core/SkRefCnt.h"
24 #include "include/core/SkScalar.h"
25 #include "include/core/SkStream.h"
26 #include "include/core/SkString.h"
27 #include "include/core/SkSurface.h"
28 #include "include/core/SkSurfaceProps.h"
29 #include "include/core/SkTextBlob.h"
30 #include "include/core/SkTypeface.h"
31 #include "include/core/SkTypes.h"
32 #include "include/encode/SkPngEncoder.h"
33 #include "include/gpu/GpuTypes.h"
34 #include "include/gpu/ganesh/GrDirectContext.h"
35 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
36 #include "include/private/base/SkTArray.h"
37 #include "include/private/base/SkTemplates.h"
38 #include "include/private/base/SkTo.h"
39 #include "src/base/SkSpinlock.h"
40 #include "src/gpu/ganesh/GrDirectContextPriv.h"
41 #include "src/gpu/ganesh/text/GrAtlasManager.h"
42 #include "src/text/gpu/TextBlobRedrawCoordinator.h"
43 #include "tests/CtsEnforcement.h"
44 #include "tests/Test.h"
45 #include "tools/fonts/FontToolUtils.h"
46 #include "tools/fonts/RandomScalerContext.h"
47 #include "tools/gpu/ganesh/GrAtlasTools.h"
48
49 #ifdef SK_BUILD_FOR_WIN
50 #include "include/ports/SkTypeface_win.h"
51 #endif
52
53 #include <algorithm>
54 #include <cstdint>
55 #include <cstring>
56 #include <string>
57
58 using namespace skia_private;
59
60 struct GrContextOptions;
61
draw(SkCanvas * canvas,int redraw,const TArray<sk_sp<SkTextBlob>> & blobs)62 static void draw(SkCanvas* canvas, int redraw, const TArray<sk_sp<SkTextBlob>>& blobs) {
63 int yOffset = 0;
64 for (int r = 0; r < redraw; r++) {
65 for (int i = 0; i < blobs.size(); i++) {
66 const auto& blob = blobs[i];
67 const SkRect& bounds = blob->bounds();
68 yOffset += SkScalarCeilToInt(bounds.height());
69 SkPaint paint;
70 canvas->drawTextBlob(blob, 0, SkIntToScalar(yOffset), paint);
71 }
72 }
73 }
74
75 static const int kWidth = 1024;
76 static const int kHeight = 768;
77
setup_always_evict_atlas(GrDirectContext * dContext)78 static void setup_always_evict_atlas(GrDirectContext* dContext) {
79 GrAtlasManagerTools::SetAtlasDimensionsToMinimum(dContext->priv().getAtlasManager());
80 }
81
82 class GrTextBlobTestingPeer {
83 public:
SetBudget(sktext::gpu::TextBlobRedrawCoordinator * cache,size_t budget)84 static void SetBudget(sktext::gpu::TextBlobRedrawCoordinator* cache, size_t budget) {
85 SkAutoSpinlock lock{cache->fSpinLock};
86 cache->fSizeBudget = budget;
87 cache->internalCheckPurge();
88 }
89 };
90
91 // This test hammers the GPU textblobcache and font atlas
text_blob_cache_inner(skiatest::Reporter * reporter,GrDirectContext * dContext,int maxTotalText,int maxGlyphID,int maxFamilies,bool normal,bool stressTest)92 static void text_blob_cache_inner(skiatest::Reporter* reporter, GrDirectContext* dContext,
93 int maxTotalText, int maxGlyphID, int maxFamilies, bool normal,
94 bool stressTest) {
95 // setup surface
96 uint32_t flags = 0;
97 SkSurfaceProps props(flags, kRGB_H_SkPixelGeometry);
98
99 // configure our context for maximum stressing of cache and atlas
100 if (stressTest) {
101 setup_always_evict_atlas(dContext);
102 GrTextBlobTestingPeer::SetBudget(dContext->priv().getTextBlobCache(), 0);
103 }
104
105 SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType,
106 kPremul_SkAlphaType);
107 auto surface(SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info, 0, &props));
108 REPORTER_ASSERT(reporter, surface);
109 if (!surface) {
110 return;
111 }
112
113 SkCanvas* canvas = surface->getCanvas();
114
115 sk_sp<SkFontMgr> fm(ToolUtils::TestFontMgr());
116
117 int count = std::min(fm->countFamilies(), maxFamilies);
118
119 // make a ton of text
120 AutoTArray<uint16_t> text(maxTotalText);
121 for (int i = 0; i < maxTotalText; i++) {
122 text[i] = i % maxGlyphID;
123 }
124
125 // generate textblobs
126 TArray<sk_sp<SkTextBlob>> blobs;
127 for (int i = 0; i < count; i++) {
128 SkFont font;
129 font.setSize(48); // draw big glyphs to really stress the atlas
130
131 SkString familyName;
132 fm->getFamilyName(i, &familyName);
133 sk_sp<SkFontStyleSet> set(fm->createStyleSet(i));
134 for (int j = 0; j < set->count(); ++j) {
135 SkFontStyle fs;
136 set->getStyle(j, &fs, nullptr);
137
138 // We use a typeface which randomy returns unexpected mask formats to fuzz
139 sk_sp<SkTypeface> orig(set->createTypeface(j));
140 if (normal) {
141 font.setTypeface(orig);
142 } else {
143 font.setTypeface(sk_make_sp<SkRandomTypeface>(orig, SkPaint(), true));
144 }
145
146 SkTextBlobBuilder builder;
147 for (int aa = 0; aa < 2; aa++) {
148 for (int subpixel = 0; subpixel < 2; subpixel++) {
149 for (int lcd = 0; lcd < 2; lcd++) {
150 font.setEdging(SkFont::Edging::kAlias);
151 if (aa) {
152 font.setEdging(SkFont::Edging::kAntiAlias);
153 if (lcd) {
154 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
155 }
156 }
157 font.setSubpixel(SkToBool(subpixel));
158 if (!SkToBool(lcd)) {
159 font.setSize(160);
160 }
161 const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font,
162 maxTotalText,
163 0, 0,
164 nullptr);
165 memcpy(run.glyphs, text.get(), maxTotalText * sizeof(uint16_t));
166 }
167 }
168 }
169 blobs.emplace_back(builder.make());
170 }
171 }
172
173 // create surface where LCD is impossible
174 info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
175 SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry);
176 auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD));
177 REPORTER_ASSERT(reporter, surface);
178 if (!surface) {
179 return;
180 }
181
182 SkCanvas* canvasNoLCD = surfaceNoLCD->getCanvas();
183
184 // test redraw
185 draw(canvas, 2, blobs);
186 draw(canvasNoLCD, 2, blobs);
187
188 // test draw after free
189 dContext->freeGpuResources();
190 draw(canvas, 1, blobs);
191
192 dContext->freeGpuResources();
193 draw(canvasNoLCD, 1, blobs);
194
195 // test draw after abandon
196 dContext->abandonContext();
197 draw(canvas, 1, blobs);
198 }
199
200 #ifdef SK_ENABLE_SMALL_PAGE
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache,reporter,ctxInfo)201 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
202 text_blob_cache_inner(reporter, ctxInfo.directContext(), 1024, 256, 30, true, true);
203 }
204 #else
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache,reporter,ctxInfo)205 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
206 text_blob_cache_inner(reporter, ctxInfo.directContext(), 1024, 256, 30, true, false);
207 }
208 #endif
209
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressCache,reporter,ctxInfo)210 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
211 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, true, true);
212 }
213
214 #ifdef SK_ENABLE_SMALL_PAGE
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal,reporter,ctxInfo)215 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
216 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, true);
217 }
218 #else
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal,reporter,ctxInfo)219 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
220 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, false);
221 }
222 #endif
223
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal,reporter,ctxInfo)224 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
225 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, true);
226 }
227
228 static const int kScreenDim = 160;
229
draw_blob(SkTextBlob * blob,SkSurface * surface,SkPoint offset)230 static SkBitmap draw_blob(SkTextBlob* blob, SkSurface* surface, SkPoint offset) {
231
232 SkPaint paint;
233
234 SkCanvas* canvas = surface->getCanvas();
235 canvas->save();
236 canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
237 canvas->translate(offset.fX, offset.fY);
238 canvas->drawTextBlob(blob, 0, 0, paint);
239 SkBitmap bitmap;
240 bitmap.allocN32Pixels(kScreenDim, kScreenDim);
241 surface->readPixels(bitmap, 0, 0);
242 canvas->restore();
243 return bitmap;
244 }
245
compare_bitmaps(const SkBitmap & expected,const SkBitmap & actual)246 static bool compare_bitmaps(const SkBitmap& expected, const SkBitmap& actual) {
247 SkASSERT(expected.width() == actual.width());
248 SkASSERT(expected.height() == actual.height());
249 for (int i = 0; i < expected.width(); ++i) {
250 for (int j = 0; j < expected.height(); ++j) {
251 SkColor expectedColor = expected.getColor(i, j);
252 SkColor actualColor = actual.getColor(i, j);
253 if (expectedColor != actualColor) {
254 return false;
255 }
256 }
257 }
258 return true;
259 }
260
make_blob()261 static sk_sp<SkTextBlob> make_blob() {
262 auto tf = ToolUtils::CreateTestTypeface("Roboto2-Regular", SkFontStyle());
263 SkFont font;
264 font.setTypeface(tf);
265 font.setSubpixel(false);
266 font.setEdging(SkFont::Edging::kAlias);
267 font.setSize(24);
268
269 static char text[] = "HekpqB";
270 static const int maxGlyphLen = sizeof(text) * 4;
271 SkGlyphID glyphs[maxGlyphLen];
272 int glyphCount =
273 font.textToGlyphs(text, sizeof(text), SkTextEncoding::kUTF8, glyphs, maxGlyphLen);
274
275 SkTextBlobBuilder builder;
276 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
277 for (int i = 0; i < glyphCount; i++) {
278 runBuffer.glyphs[i] = glyphs[i];
279 }
280 return builder.make();
281 }
282
283 // Turned off to pass on android and ios devices, which were running out of memory..
284 #if 0
285 static sk_sp<SkTextBlob> make_large_blob() {
286 auto tf = ToolUtils::CreateTestTypeface("Roboto2-Regular", SkFontStyle());
287 SkFont font;
288 font.setTypeface(tf);
289 font.setSubpixel(false);
290 font.setEdging(SkFont::Edging::kAlias);
291 font.setSize(24);
292
293 const int mallocSize = 0x3c3c3bd; // x86 size
294 std::unique_ptr<char[]> text{new char[mallocSize + 1]};
295 if (text == nullptr) {
296 return nullptr;
297 }
298 for (int i = 0; i < mallocSize; i++) {
299 text[i] = 'x';
300 }
301 text[mallocSize] = 0;
302
303 static const int maxGlyphLen = mallocSize;
304 std::unique_ptr<SkGlyphID[]> glyphs{new SkGlyphID[maxGlyphLen]};
305 int glyphCount =
306 font.textToGlyphs(
307 text.get(), mallocSize, SkTextEncoding::kUTF8, glyphs.get(), maxGlyphLen);
308 SkTextBlobBuilder builder;
309 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
310 for (int i = 0; i < glyphCount; i++) {
311 runBuffer.glyphs[i] = glyphs[i];
312 }
313 return builder.make();
314 }
315
316 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobIntegerOverflowTest, reporter, ctxInfo,
317 CtsEnforcement::kApiLevel_T) {
318 auto dContext = ctxInfo.directContext();
319 const SkImageInfo info =
320 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
321 auto surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info);
322
323 auto blob = make_large_blob();
324 int y = 40;
325 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
326 }
327 #endif
328
329 static const bool kDumpPngs = true;
330 // dump pngs needs a "good" and a "bad" directory to put the results in. This allows the use of the
331 // skdiff tool to visualize the differences.
332
write_png(const std::string & filename,const SkBitmap & bitmap)333 void write_png(const std::string& filename, const SkBitmap& bitmap) {
334 SkFILEWStream w{filename.c_str()};
335 SkASSERT_RELEASE(SkPngEncoder::Encode(&w, bitmap.pixmap(), {}));
336 w.fsync();
337 }
338
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph,reporter,ctxInfo,CtsEnforcement::kApiLevel_T)339 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph,
340 reporter,
341 ctxInfo,
342 CtsEnforcement::kApiLevel_T) {
343 auto direct = ctxInfo.directContext();
344 const SkImageInfo info =
345 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
346 auto surface = SkSurfaces::RenderTarget(direct, skgpu::Budgeted::kNo, info);
347
348 auto blob = make_blob();
349
350 for (int y = 40; y < kScreenDim - 40; y++) {
351 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
352 SkBitmap half = draw_blob(blob.get(), surface.get(), {40, y + 0.5f});
353 SkBitmap unit = draw_blob(blob.get(), surface.get(), {40, y + 1.0f});
354 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
355 REPORTER_ASSERT(reporter, isOk);
356 if (!isOk) {
357 if (kDumpPngs) {
358 {
359 std::string filename = "bad/half-y" + std::to_string(y) + ".png";
360 write_png(filename, half);
361 }
362 {
363 std::string filename = "good/half-y" + std::to_string(y) + ".png";
364 write_png(filename, base);
365 }
366 }
367 break;
368 }
369 }
370
371 // Testing the x direction across all platforms does not workout, because letter spacing can
372 // change based on non-integer advance widths, but this has been useful for diagnosing problems.
373 #if 0
374 blob = make_blob();
375 for (int x = 40; x < kScreenDim - 40; x++) {
376 SkBitmap base = draw_blob(blob.get(), surface.get(), {x + 0.0f, 40});
377 SkBitmap half = draw_blob(blob.get(), surface.get(), {x + 0.5f, 40});
378 SkBitmap unit = draw_blob(blob.get(), surface.get(), {x + 1.0f, 40});
379 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
380 REPORTER_ASSERT(reporter, isOk);
381 if (!isOk) {
382 if (kDumpPngs) {
383 {
384 std::string filename = "bad/half-x" + std::to_string(x) + ".png";
385 write_png(filename, half);
386 }
387 {
388 std::string filename = "good/half-x" + std::to_string(x) + ".png";
389 write_png(filename, base);
390 }
391 }
392 break;
393 }
394 }
395 #endif
396 }
397
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll,reporter,ctxInfo,CtsEnforcement::kApiLevel_T)398 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll,
399 reporter,
400 ctxInfo,
401 CtsEnforcement::kApiLevel_T) {
402 auto direct = ctxInfo.directContext();
403 const SkImageInfo info =
404 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
405 auto surface = SkSurfaces::RenderTarget(direct, skgpu::Budgeted::kNo, info);
406
407 auto movingBlob = make_blob();
408
409 for (SkScalar y = 40; y < 50; y += 1.0/8.0) {
410 auto expectedBlob = make_blob();
411 auto expectedBitMap = draw_blob(expectedBlob.get(), surface.get(), {40, y});
412 auto movingBitmap = draw_blob(movingBlob.get(), surface.get(), {40, y});
413 bool isOk = compare_bitmaps(expectedBitMap, movingBitmap);
414 REPORTER_ASSERT(reporter, isOk);
415 if (!isOk) {
416 if (kDumpPngs) {
417 {
418 std::string filename = "bad/scroll-y" + std::to_string(y) + ".png";
419 write_png(filename, movingBitmap);
420 }
421 {
422 std::string filename = "good/scroll-y" + std::to_string(y) + ".png";
423 write_png(filename, expectedBitMap);
424 }
425 }
426 break;
427 }
428 }
429 }
430