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
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache,reporter,ctxInfo)200 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
201 text_blob_cache_inner(reporter, ctxInfo.directContext(), 1024, 256, 30, true, false);
202 }
203
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressCache,reporter,ctxInfo)204 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
205 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, true, true);
206 }
207
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal,reporter,ctxInfo)208 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
209 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, false);
210 }
211
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal,reporter,ctxInfo)212 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
213 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, true);
214 }
215
216 static const int kScreenDim = 160;
217
draw_blob(SkTextBlob * blob,SkSurface * surface,SkPoint offset)218 static SkBitmap draw_blob(SkTextBlob* blob, SkSurface* surface, SkPoint offset) {
219
220 SkPaint paint;
221
222 SkCanvas* canvas = surface->getCanvas();
223 canvas->save();
224 canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
225 canvas->translate(offset.fX, offset.fY);
226 canvas->drawTextBlob(blob, 0, 0, paint);
227 SkBitmap bitmap;
228 bitmap.allocN32Pixels(kScreenDim, kScreenDim);
229 surface->readPixels(bitmap, 0, 0);
230 canvas->restore();
231 return bitmap;
232 }
233
compare_bitmaps(const SkBitmap & expected,const SkBitmap & actual)234 static bool compare_bitmaps(const SkBitmap& expected, const SkBitmap& actual) {
235 SkASSERT(expected.width() == actual.width());
236 SkASSERT(expected.height() == actual.height());
237 for (int i = 0; i < expected.width(); ++i) {
238 for (int j = 0; j < expected.height(); ++j) {
239 SkColor expectedColor = expected.getColor(i, j);
240 SkColor actualColor = actual.getColor(i, j);
241 if (expectedColor != actualColor) {
242 return false;
243 }
244 }
245 }
246 return true;
247 }
248
make_blob()249 static sk_sp<SkTextBlob> make_blob() {
250 auto tf = ToolUtils::CreateTestTypeface("Roboto2-Regular", SkFontStyle());
251 SkFont font;
252 font.setTypeface(tf);
253 font.setSubpixel(false);
254 font.setEdging(SkFont::Edging::kAlias);
255 font.setSize(24);
256
257 static char text[] = "HekpqB";
258 static const int maxGlyphLen = sizeof(text) * 4;
259 SkGlyphID glyphs[maxGlyphLen];
260 int glyphCount =
261 font.textToGlyphs(text, sizeof(text), SkTextEncoding::kUTF8, glyphs, maxGlyphLen);
262
263 SkTextBlobBuilder builder;
264 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
265 for (int i = 0; i < glyphCount; i++) {
266 runBuffer.glyphs[i] = glyphs[i];
267 }
268 return builder.make();
269 }
270
271 // Turned off to pass on android and ios devices, which were running out of memory..
272 #if 0
273 static sk_sp<SkTextBlob> make_large_blob() {
274 auto tf = ToolUtils::CreateTestTypeface("Roboto2-Regular", SkFontStyle());
275 SkFont font;
276 font.setTypeface(tf);
277 font.setSubpixel(false);
278 font.setEdging(SkFont::Edging::kAlias);
279 font.setSize(24);
280
281 const int mallocSize = 0x3c3c3bd; // x86 size
282 std::unique_ptr<char[]> text{new char[mallocSize + 1]};
283 if (text == nullptr) {
284 return nullptr;
285 }
286 for (int i = 0; i < mallocSize; i++) {
287 text[i] = 'x';
288 }
289 text[mallocSize] = 0;
290
291 static const int maxGlyphLen = mallocSize;
292 std::unique_ptr<SkGlyphID[]> glyphs{new SkGlyphID[maxGlyphLen]};
293 int glyphCount =
294 font.textToGlyphs(
295 text.get(), mallocSize, SkTextEncoding::kUTF8, glyphs.get(), maxGlyphLen);
296 SkTextBlobBuilder builder;
297 const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);
298 for (int i = 0; i < glyphCount; i++) {
299 runBuffer.glyphs[i] = glyphs[i];
300 }
301 return builder.make();
302 }
303
304 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobIntegerOverflowTest, reporter, ctxInfo,
305 CtsEnforcement::kApiLevel_T) {
306 auto dContext = ctxInfo.directContext();
307 const SkImageInfo info =
308 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
309 auto surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info);
310
311 auto blob = make_large_blob();
312 int y = 40;
313 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
314 }
315 #endif
316
317 static const bool kDumpPngs = true;
318 // dump pngs needs a "good" and a "bad" directory to put the results in. This allows the use of the
319 // skdiff tool to visualize the differences.
320
write_png(const std::string & filename,const SkBitmap & bitmap)321 void write_png(const std::string& filename, const SkBitmap& bitmap) {
322 SkFILEWStream w{filename.c_str()};
323 SkASSERT_RELEASE(SkPngEncoder::Encode(&w, bitmap.pixmap(), {}));
324 w.fsync();
325 }
326
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph,reporter,ctxInfo,CtsEnforcement::kApiLevel_T)327 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph,
328 reporter,
329 ctxInfo,
330 CtsEnforcement::kApiLevel_T) {
331 auto direct = ctxInfo.directContext();
332 const SkImageInfo info =
333 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
334 auto surface = SkSurfaces::RenderTarget(direct, skgpu::Budgeted::kNo, info);
335
336 auto blob = make_blob();
337
338 for (int y = 40; y < kScreenDim - 40; y++) {
339 SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});
340 SkBitmap half = draw_blob(blob.get(), surface.get(), {40, y + 0.5f});
341 SkBitmap unit = draw_blob(blob.get(), surface.get(), {40, y + 1.0f});
342 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
343 REPORTER_ASSERT(reporter, isOk);
344 if (!isOk) {
345 if (kDumpPngs) {
346 {
347 std::string filename = "bad/half-y" + std::to_string(y) + ".png";
348 write_png(filename, half);
349 }
350 {
351 std::string filename = "good/half-y" + std::to_string(y) + ".png";
352 write_png(filename, base);
353 }
354 }
355 break;
356 }
357 }
358
359 // Testing the x direction across all platforms does not workout, because letter spacing can
360 // change based on non-integer advance widths, but this has been useful for diagnosing problems.
361 #if 0
362 blob = make_blob();
363 for (int x = 40; x < kScreenDim - 40; x++) {
364 SkBitmap base = draw_blob(blob.get(), surface.get(), {x + 0.0f, 40});
365 SkBitmap half = draw_blob(blob.get(), surface.get(), {x + 0.5f, 40});
366 SkBitmap unit = draw_blob(blob.get(), surface.get(), {x + 1.0f, 40});
367 bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);
368 REPORTER_ASSERT(reporter, isOk);
369 if (!isOk) {
370 if (kDumpPngs) {
371 {
372 std::string filename = "bad/half-x" + std::to_string(x) + ".png";
373 write_png(filename, half);
374 }
375 {
376 std::string filename = "good/half-x" + std::to_string(x) + ".png";
377 write_png(filename, base);
378 }
379 }
380 break;
381 }
382 }
383 #endif
384 }
385
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll,reporter,ctxInfo,CtsEnforcement::kApiLevel_T)386 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll,
387 reporter,
388 ctxInfo,
389 CtsEnforcement::kApiLevel_T) {
390 auto direct = ctxInfo.directContext();
391 const SkImageInfo info =
392 SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);
393 auto surface = SkSurfaces::RenderTarget(direct, skgpu::Budgeted::kNo, info);
394
395 auto movingBlob = make_blob();
396
397 for (SkScalar y = 40; y < 50; y += 1.0/8.0) {
398 auto expectedBlob = make_blob();
399 auto expectedBitMap = draw_blob(expectedBlob.get(), surface.get(), {40, y});
400 auto movingBitmap = draw_blob(movingBlob.get(), surface.get(), {40, y});
401 bool isOk = compare_bitmaps(expectedBitMap, movingBitmap);
402 REPORTER_ASSERT(reporter, isOk);
403 if (!isOk) {
404 if (kDumpPngs) {
405 {
406 std::string filename = "bad/scroll-y" + std::to_string(y) + ".png";
407 write_png(filename, movingBitmap);
408 }
409 {
410 std::string filename = "good/scroll-y" + std::to_string(y) + ".png";
411 write_png(filename, expectedBitMap);
412 }
413 }
414 break;
415 }
416 }
417 }
418