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