Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
81
83public:
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
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);
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) {
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
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
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
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
uint16_t glyphs[5]
int count
@ 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 DEF_GANESH_TEST_FOR_MOCK_CONTEXT(name, reporter, context_info)
Definition Test.h:450
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
#define DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(name, reporter, context_info, ctsEnforcement)
Definition Test.h:434
static void draw(SkCanvas *canvas, int redraw, const TArray< sk_sp< SkTextBlob > > &blobs)
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)
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:465
void translate(SkScalar dx, SkScalar dy)
sk_sp< SkSurface > makeSurface(const SkImageInfo &info, const SkSurfaceProps *props=nullptr)
void drawColor(SkColor color, SkBlendMode mode=SkBlendMode::kSrcOver)
Definition SkCanvas.h:1182
int save()
Definition SkCanvas.cpp:451
void drawTextBlob(const SkTextBlob *blob, SkScalar x, SkScalar y, const SkPaint &paint)
@ 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:243
const Paint & paint
VkSurfaceKHR surface
Definition main.cc:49
float SkScalar
Definition extension.cpp:12
FlutterSemanticsFlag flags
std::u16string text
double y
double x
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
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()
font
Font Metadata and Metrics.
Definition run.py:1
SkScalar w
Point offset
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)