Flutter Engine
The Flutter Engine
TextBlobCacheTest.cpp
Go to the documentation of this file.
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
15#include "include/core/SkFont.h"
22#include "include/core/SkRect.h"
39#include "src/base/SkSpinlock.h"
44#include "tests/Test.h"
48
49#ifdef SK_BUILD_FOR_WIN
51#endif
52
53#include <algorithm>
54#include <cstdint>
55#include <cstring>
56#include <string>
57
58using namespace skia_private;
59
60struct GrContextOptions;
61
62static 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());
70 canvas->drawTextBlob(blob, 0, SkIntToScalar(yOffset), paint);
71 }
72 }
73}
74
75static const int kWidth = 1024;
76static const int kHeight = 768;
77
80}
81
83public:
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
93 int maxTotalText, int maxGlyphID, int maxFamilies, bool normal,
94 bool stressTest) {
95 // setup surface
96 uint32_t flags = 0;
98
99 // configure our context for maximum stressing of cache and atlas
100 if (stressTest) {
101 setup_always_evict_atlas(dContext);
103 }
104
107 auto surface(SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info, 0, &props));
109 if (!surface) {
110 return;
111 }
112
113 SkCanvas* canvas = surface->getCanvas();
114
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
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);
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
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) {
153 if (lcd) {
155 }
156 }
157 font.setSubpixel(SkToBool(subpixel));
158 if (!SkToBool(lcd)) {
159 font.setSize(160);
160 }
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
176 auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD));
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
201 text_blob_cache_inner(reporter, ctxInfo.directContext(), 1024, 256, 30, true, false);
202}
203
204DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
205 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, true, true);
206}
207
208DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
209 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, false);
210}
211
212DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
213 text_blob_cache_inner(reporter, ctxInfo.directContext(), 256, 256, 10, false, true);
214}
215
216static const int kScreenDim = 160;
217
219
221
222 SkCanvas* canvas = surface->getCanvas();
223 canvas->save();
225 canvas->translate(offset.fX, offset.fY);
226 canvas->drawTextBlob(blob, 0, 0, paint);
228 bitmap.allocN32Pixels(kScreenDim, kScreenDim);
229 surface->readPixels(bitmap, 0, 0);
230 canvas->restore();
231 return bitmap;
232}
233
234static 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
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
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
273static 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);
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
304DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobIntegerOverflowTest, reporter, ctxInfo,
306 auto dContext = ctxInfo.directContext();
307 const SkImageInfo 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
317static 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
321void write_png(const std::string& filename, const SkBitmap& bitmap) {
322 SkFILEWStream w{filename.c_str()};
324 w.fsync();
325}
326
328 reporter,
329 ctxInfo,
331 auto direct = ctxInfo.directContext();
332 const SkImageInfo 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);
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);
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
387 reporter,
388 ctxInfo,
390 auto direct = ctxInfo.directContext();
391 const SkImageInfo 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);
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}
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
reporter
Definition: FontMgrTest.cpp:39
uint16_t glyphs[5]
Definition: FontMgrTest.cpp:46
int count
Definition: FontMgrTest.cpp:50
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition: SkAlphaType.h:29
#define SkASSERT_RELEASE(cond)
Definition: SkAssert.h:100
#define SkASSERT(cond)
Definition: SkAssert.h:116
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition: SkColorType.h:24
uint32_t SkColor
Definition: SkColor.h:37
constexpr SkColor SK_ColorWHITE
Definition: SkColor.h:122
@ kUTF8
uses bytes to represent UTF-8 or ASCII
#define SkScalarCeilToInt(x)
Definition: SkScalar.h:36
#define SkIntToScalar(x)
Definition: SkScalar.h:57
@ kUnknown_SkPixelGeometry
@ kRGB_H_SkPixelGeometry
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
uint16_t SkGlyphID
Definition: SkTypes.h:179
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
static void draw(SkCanvas *canvas, int redraw, const TArray< sk_sp< SkTextBlob > > &blobs)
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph, reporter, ctxInfo, CtsEnforcement::kApiLevel_T)
static void setup_always_evict_atlas(GrDirectContext *dContext)
static sk_sp< SkTextBlob > make_blob()
static const int kHeight
static bool compare_bitmaps(const SkBitmap &expected, const SkBitmap &actual)
DEF_GANESH_TEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo)
static const int kScreenDim
static const int kWidth
static const bool kDumpPngs
static void text_blob_cache_inner(skiatest::Reporter *reporter, GrDirectContext *dContext, int maxTotalText, int maxGlyphID, int maxFamilies, bool normal, bool stressTest)
static SkBitmap draw_blob(SkTextBlob *blob, SkSurface *surface, SkPoint offset)
void write_png(const std::string &filename, const SkBitmap &bitmap)
static void SetAtlasDimensionsToMinimum(GrAtlasManager *)
GrAtlasManager * getAtlasManager()
GrDirectContextPriv priv()
void abandonContext() override
sktext::gpu::TextBlobRedrawCoordinator * getTextBlobCache()
static void SetBudget(sktext::gpu::TextBlobRedrawCoordinator *cache, size_t budget)
SkColor getColor(int x, int y) const
Definition: SkBitmap.h:874
int width() const
Definition: SkBitmap.h:149
int height() const
Definition: SkBitmap.h:158
void restore()
Definition: SkCanvas.cpp:461
void translate(SkScalar dx, SkScalar dy)
Definition: SkCanvas.cpp:1278
sk_sp< SkSurface > makeSurface(const SkImageInfo &info, const SkSurfaceProps *props=nullptr)
Definition: SkCanvas.cpp:1195
void drawColor(SkColor color, SkBlendMode mode=SkBlendMode::kSrcOver)
Definition: SkCanvas.h:1182
int save()
Definition: SkCanvas.cpp:447
void drawTextBlob(const SkTextBlob *blob, SkScalar x, SkScalar y, const SkPaint &paint)
Definition: SkCanvas.cpp:2484
sk_sp< SkFontStyleSet > createStyleSet(int index) const
Definition: SkFontMgr.cpp:101
void getFamilyName(int index, SkString *familyName) const
Definition: SkFontMgr.cpp:97
int countFamilies() const
Definition: SkFontMgr.cpp:93
Definition: SkFont.h:35
@ kAntiAlias
may have transparent pixels on glyph edges
@ kAlias
no transparent pixels on glyph edges
@ kSubpixelAntiAlias
glyph positioned in pixel using transparency
T & emplace_back(Args &&... args)
Definition: SkTArray.h:248
const Paint & paint
Definition: color_source.cc:38
VkSurfaceKHR surface
Definition: main.cc:49
float SkScalar
Definition: extension.cpp:12
FlutterSemanticsFlag flags
static float min(float r, float g, float b)
Definition: hsl.cpp:48
std::u16string text
double y
double x
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
Optional< SkRect > bounds
Definition: SkRecords.h:189
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
sk_sp< SkTypeface > CreateTestTypeface(const char *name, SkFontStyle style)
sk_sp< SkFontMgr > TestFontMgr()
Definition: bitmap.py:1
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent cache
Definition: switches.h:191
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition: switches.h:76
font
Font Metadata and Metrics.
Definition: run.py:1
static SkString to_string(int n)
Definition: nanobench.cpp:119
SkScalar w
SeparatedVector2 offset
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)