Flutter Engine
 
Loading...
Searching...
No Matches
typographer_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
7#include "gtest/gtest.h"
16#include "third_party/skia/include/core/SkFont.h"
17#include "third_party/skia/include/core/SkFontMgr.h"
18#include "third_party/skia/include/core/SkRect.h"
19#include "third_party/skia/include/core/SkTextBlob.h"
20#include "third_party/skia/include/core/SkTypeface.h"
21#include "txt/platform.h"
22
23// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
24// NOLINTBEGIN(bugprone-unchecked-optional-access)
25
26namespace impeller {
27namespace testing {
28
31
32static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
33 Context& context,
34 const TypographerContext* typographer_context,
35 HostBuffer& data_host_buffer,
37 Rational scale,
38 const std::shared_ptr<GlyphAtlasContext>& atlas_context,
39 const std::shared_ptr<TextFrame>& frame) {
40 frame->SetPerFrameData(scale, {0, 0}, Matrix(), std::nullopt);
41 return typographer_context->CreateGlyphAtlas(context, type, data_host_buffer,
42 atlas_context, {frame});
43}
44
45static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
46 Context& context,
47 const TypographerContext* typographer_context,
48 HostBuffer& data_host_buffer,
50 Rational scale,
51 const std::shared_ptr<GlyphAtlasContext>& atlas_context,
52 const std::vector<std::shared_ptr<TextFrame>>& frames,
53 const std::vector<std::optional<GlyphProperties>>& properties) {
54 size_t offset = 0;
55 for (auto& frame : frames) {
56 frame->SetPerFrameData(scale, {0, 0}, Matrix(), properties[offset++]);
57 }
58 return typographer_context->CreateGlyphAtlas(context, type, data_host_buffer,
59 atlas_context, frames);
60}
61
62TEST_P(TypographerTest, CanConvertTextBlob) {
64 auto blob = SkTextBlob::MakeFromString(
65 "the quick brown fox jumped over the lazy dog.", font);
66 ASSERT_TRUE(blob);
67 auto frame = MakeTextFrameFromTextBlobSkia(blob);
68 ASSERT_EQ(frame->GetRunCount(), 1u);
69 for (const auto& run : frame->GetRuns()) {
70 ASSERT_TRUE(run.IsValid());
71 ASSERT_EQ(run.GetGlyphCount(), 45u);
72 }
73}
74
75TEST_P(TypographerTest, CanCreateRenderContext) {
76 auto context = TypographerContextSkia::Make();
77 ASSERT_TRUE(context && context->IsValid());
78}
79
80TEST_P(TypographerTest, CanCreateGlyphAtlas) {
81 auto context = TypographerContextSkia::Make();
82 auto atlas_context =
83 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
84 auto data_host_buffer = HostBuffer::Create(
85 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
86 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
87 ASSERT_TRUE(context && context->IsValid());
88 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
89 auto blob = SkTextBlob::MakeFromString("hello", sk_font);
90 ASSERT_TRUE(blob);
91 auto atlas =
92 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
94 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
95
96 ASSERT_NE(atlas, nullptr);
97 ASSERT_NE(atlas->GetTexture(), nullptr);
98 ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap);
99 ASSERT_EQ(atlas->GetGlyphCount(), 4llu);
100
101 std::optional<impeller::ScaledFont> first_scaled_font;
102 std::optional<impeller::SubpixelGlyph> first_glyph;
103 Rect first_rect;
104 atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
105 const SubpixelGlyph& glyph,
106 const Rect& rect) -> bool {
107 first_scaled_font = scaled_font;
108 first_glyph = glyph;
109 first_rect = rect;
110 return false;
111 });
112
113 ASSERT_TRUE(first_scaled_font.has_value());
114 ASSERT_TRUE(atlas
115 ->FindFontGlyphBounds(
116 {first_scaled_font.value(), first_glyph.value()})
117 .has_value());
118}
119
120TEST_P(TypographerTest, LazyAtlasTracksColor) {
121 auto data_host_buffer = HostBuffer::Create(
122 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
123 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
124#if FML_OS_MACOSX
125 auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc");
126#else
127 auto mapping = flutter::testing::OpenFixtureAsSkData("NotoColorEmoji.ttf");
128#endif
129 ASSERT_TRUE(mapping);
130 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
131 SkFont emoji_font(font_mgr->makeFromData(mapping), 50.0);
132 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
133
134 auto blob = SkTextBlob::MakeFromString("hello", sk_font);
135 ASSERT_TRUE(blob);
136 auto frame = MakeTextFrameFromTextBlobSkia(blob);
137
138 ASSERT_FALSE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
139
141
142 lazy_atlas.AddTextFrame(frame, Rational(1), {0, 0}, Matrix(), {});
143
145 SkTextBlob::MakeFromString("😀 ", emoji_font));
146
147 ASSERT_TRUE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
148
149 lazy_atlas.AddTextFrame(frame, Rational(1), {0, 0}, Matrix(), {});
150
151 // Creates different atlases for color and red bitmap.
152 auto color_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
153 *GetContext(), *data_host_buffer, GlyphAtlas::Type::kColorBitmap);
154
155 auto bitmap_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
156 *GetContext(), *data_host_buffer, GlyphAtlas::Type::kAlphaBitmap);
157
158 ASSERT_FALSE(color_atlas == bitmap_atlas);
159}
160
161TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
162 auto context = TypographerContextSkia::Make();
163 auto atlas_context =
164 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
165 auto data_host_buffer = HostBuffer::Create(
166 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
167 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
168 ASSERT_TRUE(context && context->IsValid());
169 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
170 auto blob = SkTextBlob::MakeFromString("AGH", sk_font);
171 ASSERT_TRUE(blob);
172 auto atlas =
173 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
175 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
176 ASSERT_NE(atlas, nullptr);
177 ASSERT_NE(atlas->GetTexture(), nullptr);
178
179 EXPECT_EQ(atlas->GetTexture()->GetSize().width, 4096u);
180 EXPECT_EQ(atlas->GetTexture()->GetSize().height, 1024u);
181}
182
183TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
184 auto context = TypographerContextSkia::Make();
185 auto atlas_context =
186 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
187 auto data_host_buffer = HostBuffer::Create(
188 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
189 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
190 ASSERT_TRUE(context && context->IsValid());
191 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
192 auto blob = SkTextBlob::MakeFromString("spooky skellingtons", sk_font);
193 ASSERT_TRUE(blob);
194 auto atlas =
195 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
197 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
198 ASSERT_NE(atlas, nullptr);
199 ASSERT_NE(atlas->GetTexture(), nullptr);
200 ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
201
202 // now attempt to re-create an atlas with the same text blob.
203
204 auto next_atlas =
205 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
207 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
208 ASSERT_EQ(atlas, next_atlas);
209 ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas);
210}
211
212TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
213 auto data_host_buffer = HostBuffer::Create(
214 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
215 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
216 auto context = TypographerContextSkia::Make();
217 auto atlas_context =
218 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
219 ASSERT_TRUE(context && context->IsValid());
220
221 const char* test_string =
222 "QWERTYUIOPASDFGHJKLZXCVBNMqewrtyuiopasdfghjklzxcvbnm,.<>[]{};':"
223 "2134567890-=!@#$%^&*()_+"
224 "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›fifl‡°·‚—±Œ„´‰Á¨Ø∏”’/"
225 "* Í˝ */¸˛Ç◊ı˜Â¯˘¿";
226
227 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
228 auto blob = SkTextBlob::MakeFromString(test_string, sk_font);
229 ASSERT_TRUE(blob);
230
231 size_t size_count = 8;
232 std::vector<std::shared_ptr<TextFrame>> frames;
233 for (size_t index = 0; index < size_count; index += 1) {
234 frames.push_back(MakeTextFrameFromTextBlobSkia(blob));
235 frames.back()->SetPerFrameData(Rational(6 * index, 10), {0, 0}, Matrix(),
236 {});
237 };
238 auto atlas =
239 context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap,
240 *data_host_buffer, atlas_context, frames);
241 ASSERT_NE(atlas, nullptr);
242 ASSERT_NE(atlas->GetTexture(), nullptr);
243
244 std::set<uint16_t> unique_glyphs;
245 std::vector<uint16_t> total_glyphs;
246 atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
247 const SubpixelGlyph& glyph, const Rect& rect) {
248 unique_glyphs.insert(glyph.glyph.index);
249 total_glyphs.push_back(glyph.glyph.index);
250 return true;
251 });
252
253 // These numbers may be different due to subpixel positions.
254 EXPECT_LE(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
255 EXPECT_EQ(total_glyphs.size(), atlas->GetGlyphCount());
256
257 EXPECT_TRUE(atlas->GetGlyphCount() > 0);
258 EXPECT_TRUE(atlas->GetTexture()->GetSize().width > 0);
259 EXPECT_TRUE(atlas->GetTexture()->GetSize().height > 0);
260}
261
262TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
263 auto data_host_buffer = HostBuffer::Create(
264 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
265 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
266 auto context = TypographerContextSkia::Make();
267 auto atlas_context =
268 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
269 ASSERT_TRUE(context && context->IsValid());
270 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
271 auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
272 ASSERT_TRUE(blob);
273 auto atlas =
274 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
276 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
277 auto old_packer = atlas_context->GetRectPacker();
278
279 ASSERT_NE(atlas, nullptr);
280 ASSERT_NE(atlas->GetTexture(), nullptr);
281 ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
282
283 auto* first_texture = atlas->GetTexture().get();
284
285 // Now create a new glyph atlas with a nearly identical blob.
286
287 auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font);
288 auto next_atlas =
289 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
291 atlas_context, MakeTextFrameFromTextBlobSkia(blob2));
292 ASSERT_EQ(atlas, next_atlas);
293 auto* second_texture = next_atlas->GetTexture().get();
294
295 auto new_packer = atlas_context->GetRectPacker();
296
297 ASSERT_EQ(second_texture, first_texture);
298 ASSERT_EQ(old_packer, new_packer);
299}
300
301TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) {
302 auto data_host_buffer = HostBuffer::Create(
303 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
304 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
305#if FML_OS_MACOSX
306 auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc");
307#else
308 auto mapping = flutter::testing::OpenFixtureAsSkData("NotoColorEmoji.ttf");
309#endif
310 ASSERT_TRUE(mapping);
311 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
312 SkFont emoji_font(font_mgr->makeFromData(mapping), 50.0);
313
314 auto context = TypographerContextSkia::Make();
315 auto atlas_context =
316 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kColorBitmap);
317
318 // Create two frames with the same character and a different color, expect
319 // that it adds a character.
321 SkTextBlob::MakeFromString("😂", emoji_font));
322 auto frame_2 = MakeTextFrameFromTextBlobSkia(
323 SkTextBlob::MakeFromString("😂", emoji_font));
324 std::vector<std::optional<GlyphProperties>> properties = {
327 };
328
329 auto next_atlas =
330 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
332 atlas_context, {frame, frame_2}, properties);
333
334 EXPECT_EQ(next_atlas->GetGlyphCount(), 2u);
335}
336
337TEST_P(TypographerTest, GlyphColorIsIgnoredForNonEmojiFonts) {
338 auto data_host_buffer = HostBuffer::Create(
339 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
340 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
341 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
342 sk_sp<SkTypeface> typeface =
343 font_mgr->matchFamilyStyle("Arial", SkFontStyle::Normal());
344 SkFont sk_font(typeface, 0.5f);
345
346 auto context = TypographerContextSkia::Make();
347 auto atlas_context =
348 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kColorBitmap);
349
350 // Create two frames with the same character and a different color, but as a
351 // non-emoji font the text frame constructor will ignore it.
352 auto frame =
353 MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font));
354 auto frame_2 =
355 MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font));
356 std::vector<std::optional<GlyphProperties>> properties = {
359 };
360
361 auto next_atlas =
362 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
364 atlas_context, {frame, frame_2}, properties);
365
366 EXPECT_EQ(next_atlas->GetGlyphCount(), 1u);
367}
368
369TEST_P(TypographerTest, RectanglePackerAddsNonoverlapingRectangles) {
370 auto packer = RectanglePacker::Factory(200, 100);
371 ASSERT_NE(packer, nullptr);
372 ASSERT_EQ(packer->PercentFull(), 0);
373
374 const SkIRect packer_area = SkIRect::MakeXYWH(0, 0, 200, 100);
375
376 IPoint16 first_output = {-1, -1}; // Fill with sentinel values
377 ASSERT_TRUE(packer->AddRect(20, 20, &first_output));
378 // Make sure the rectangle is placed such that it is inside the bounds of
379 // the packer's area.
380 const SkIRect first_rect =
381 SkIRect::MakeXYWH(first_output.x(), first_output.y(), 20, 20);
382 ASSERT_TRUE(SkIRect::Intersects(packer_area, first_rect));
383
384 // Initial area was 200 x 100 = 20_000
385 // We added 20x20 = 400. 400 / 20_000 == 0.02 == 2%
386 ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.02));
387
388 IPoint16 second_output = {-1, -1};
389 ASSERT_TRUE(packer->AddRect(140, 90, &second_output));
390 const SkIRect second_rect =
391 SkIRect::MakeXYWH(second_output.x(), second_output.y(), 140, 90);
392 // Make sure the rectangle is placed such that it is inside the bounds of
393 // the packer's area but not in the are of the first rectangle.
394 ASSERT_TRUE(SkIRect::Intersects(packer_area, second_rect));
395 ASSERT_FALSE(SkIRect::Intersects(first_rect, second_rect));
396
397 // We added another 90 x 140 = 12_600 units, now taking us to 13_000
398 // 13_000 / 20_000 == 0.65 == 65%
399 ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.65));
400
401 // There's enough area to add this rectangle, but no space big enough for
402 // the 50 units of width.
403 IPoint16 output;
404 ASSERT_FALSE(packer->AddRect(50, 50, &output));
405 // Should be unchanged.
406 ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.65));
407
408 packer->Reset();
409 // Should be empty now.
410 ASSERT_EQ(packer->PercentFull(), 0);
411}
412
413TEST(TypographerTest, RectanglePackerFillsRows) {
414 auto skyline = RectanglePacker::Factory(257, 256);
415
416 // Fill up the first row.
417 IPoint16 loc;
418 for (auto i = 0u; i < 16; i++) {
419 skyline->AddRect(16, 16, &loc);
420 }
421 // Last rectangle still in first row.
422 EXPECT_EQ(loc.x(), 256 - 16);
423 EXPECT_EQ(loc.y(), 0);
424
425 // Fill up second row.
426 for (auto i = 0u; i < 16; i++) {
427 skyline->AddRect(16, 16, &loc);
428 }
429
430 EXPECT_EQ(loc.x(), 256 - 16);
431 EXPECT_EQ(loc.y(), 16);
432}
433
434TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) {
435 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
436 GTEST_SKIP() << "Atlas growth isn't supported for OpenGLES currently.";
437 }
438
439 auto data_host_buffer = HostBuffer::Create(
440 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
441 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
442 auto context = TypographerContextSkia::Make();
443 auto atlas_context =
444 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
445 ASSERT_TRUE(context && context->IsValid());
446 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
447 auto blob = SkTextBlob::MakeFromString("A", sk_font);
448 ASSERT_TRUE(blob);
449 auto atlas =
450 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
452 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
453 // Continually append new glyphs until the glyph size grows to the maximum.
454 // Note that the sizes here are more or less experimentally determined, but
455 // the important expectation is that the atlas size will shrink again after
456 // growing to the maximum size.
457 constexpr ISize expected_sizes[13] = {
458 {4096, 4096}, //
459 {4096, 4096}, //
460 {4096, 8192}, //
461 {4096, 8192}, //
462 {4096, 8192}, //
463 {4096, 8192}, //
464 {4096, 16384}, //
465 {4096, 16384}, //
466 {4096, 16384}, //
467 {4096, 16384}, //
468 {4096, 16384}, //
469 {4096, 16384}, //
470 {4096, 4096} // Shrinks!
471 };
472
473 SkFont sk_font_small = flutter::testing::CreateTestFontOfSize(10);
474
475 for (int i = 0; i < 13; i++) {
476 SkTextBlobBuilder builder;
477
478 auto add_char = [&](const SkFont& sk_font, char c) {
479 int count = sk_font.countText(&c, 1, SkTextEncoding::kUTF8);
480 auto buffer = builder.allocRunPos(sk_font, count);
481 sk_font.textToGlyphs(&c, 1, SkTextEncoding::kUTF8,
482 {buffer.glyphs, count});
483 sk_font.getPos({buffer.glyphs, count}, {buffer.points(), count},
484 {0, 0} /*=origin*/);
485 };
486
487 SkFont sk_font = flutter::testing::CreateTestFontOfSize(50 + i);
488 add_char(sk_font, 'A');
489 add_char(sk_font_small, 'B');
490 auto blob = builder.make();
491
492 atlas =
493 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
495 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
496 ASSERT_TRUE(!!atlas);
497 EXPECT_EQ(atlas->GetTexture()->GetTextureDescriptor().size,
498 expected_sizes[i]);
499 }
500
501 // The final atlas should contain both the "A" glyph (which was not present
502 // in the previous atlas) and the "B" glyph (which existed in the previous
503 // atlas).
504 ASSERT_EQ(atlas->GetGlyphCount(), 2u);
505}
506
507TEST_P(TypographerTest, TextFrameInitialBoundsArePlaceholder) {
509 auto blob = SkTextBlob::MakeFromString(
510 "the quick brown fox jumped over the lazy dog.", font);
511 ASSERT_TRUE(blob);
512 auto frame = MakeTextFrameFromTextBlobSkia(blob);
513
514 EXPECT_FALSE(frame->IsFrameComplete());
515
516 auto context = TypographerContextSkia::Make();
517 auto atlas_context =
518 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
519 auto data_host_buffer = HostBuffer::Create(
520 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
521 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
522
523 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
525 /*scale=*/Rational(1), atlas_context, frame);
526
527 // The glyph position in the atlas was not known when this value
528 // was recorded. It is marked as a placeholder.
529 EXPECT_TRUE(frame->IsFrameComplete());
530 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
531
532 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
534 /*scale=*/Rational(1), atlas_context, frame);
535
536 // The second time the glyph is rendered, the bounds are correcly known.
537 EXPECT_TRUE(frame->IsFrameComplete());
538 EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
539}
540
541TEST_P(TypographerTest, TextFrameInvalidationWithScale) {
543 auto blob = SkTextBlob::MakeFromString(
544 "the quick brown fox jumped over the lazy dog.", font);
545 ASSERT_TRUE(blob);
546 auto frame = MakeTextFrameFromTextBlobSkia(blob);
547
548 EXPECT_FALSE(frame->IsFrameComplete());
549
550 auto context = TypographerContextSkia::Make();
551 auto atlas_context =
552 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
553 auto data_host_buffer = HostBuffer::Create(
554 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
555 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
556
557 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
559 /*scale=*/Rational(1), atlas_context, frame);
560
561 // The glyph position in the atlas was not known when this value
562 // was recorded. It is marked as a placeholder.
563 EXPECT_TRUE(frame->IsFrameComplete());
564 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
565
566 // Change the scale and the glyph data will still be a placeholder, as the
567 // old data is no longer valid.
568 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
570 /*scale=*/Rational(2), atlas_context, frame);
571
572 // The second time the glyph is rendered, the bounds are correcly known.
573 EXPECT_TRUE(frame->IsFrameComplete());
574 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
575}
576
577TEST_P(TypographerTest, TextFrameAtlasGenerationTracksState) {
579 auto blob = SkTextBlob::MakeFromString(
580 "the quick brown fox jumped over the lazy dog.", font);
581 ASSERT_TRUE(blob);
582 auto frame = MakeTextFrameFromTextBlobSkia(blob);
583
584 EXPECT_FALSE(frame->IsFrameComplete());
585
586 auto context = TypographerContextSkia::Make();
587 auto atlas_context =
588 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
589 auto data_host_buffer = HostBuffer::Create(
590 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
591 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
592
593 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
595 /*scale=*/Rational(1), atlas_context, frame);
596
597 // The glyph position in the atlas was not known when this value
598 // was recorded. It is marked as a placeholder.
599 EXPECT_TRUE(frame->IsFrameComplete());
600 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
601 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
602 // OpenGLES must always increase the atlas backend if the texture grows.
603 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
604 } else {
605 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
606 }
607
608 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
610 /*scale=*/Rational(1), atlas_context, frame);
611
612 // The second time the glyph is rendered, the bounds are correcly known.
613 EXPECT_TRUE(frame->IsFrameComplete());
614 EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
615 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
616 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
617 } else {
618 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
619 }
620
621 // Force increase the generation.
622 atlas_context->GetGlyphAtlas()->SetAtlasGeneration(2u);
623 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
625 /*scale=*/Rational(1), atlas_context, frame);
626
627 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 2u);
628}
629
630TEST_P(TypographerTest, InvalidAtlasForcesRepopulation) {
632 auto blob = SkTextBlob::MakeFromString(
633 "the quick brown fox jumped over the lazy dog.", font);
634 ASSERT_TRUE(blob);
635 auto frame = MakeTextFrameFromTextBlobSkia(blob);
636
637 EXPECT_FALSE(frame->IsFrameComplete());
638
639 auto context = TypographerContextSkia::Make();
640 auto atlas_context =
641 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
642 auto data_host_buffer = HostBuffer::Create(
643 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
644 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
645
646 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
648 /*scale=*/Rational(1), atlas_context, frame);
649
650 // The glyph position in the atlas was not known when this value
651 // was recorded. It is marked as a placeholder.
652 EXPECT_TRUE(frame->IsFrameComplete());
653 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
654 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
655 // OpenGLES must always increase the atlas backend if the texture grows.
656 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
657 } else {
658 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
659 }
660
661 auto second_context = TypographerContextSkia::Make();
662 auto second_atlas_context =
663 second_context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
664
665 EXPECT_FALSE(second_atlas_context->GetGlyphAtlas()->IsValid());
666
667 atlas = CreateGlyphAtlas(*GetContext(), second_context.get(),
668 *data_host_buffer, GlyphAtlas::Type::kAlphaBitmap,
669 /*scale=*/Rational(1), second_atlas_context, frame);
670
671 EXPECT_TRUE(second_atlas_context->GetGlyphAtlas()->IsValid());
672}
673
674} // namespace testing
675} // namespace impeller
676
677// NOLINTEND(bugprone-unchecked-optional-access)
GLenum type
To do anything rendering related with Impeller, you need a context.
Definition context.h:65
Type
Describes how the glyphs are represented in the texture.
Definition glyph_atlas.h:41
static std::shared_ptr< HostBuffer > Create(const std::shared_ptr< Allocator > &allocator, const std::shared_ptr< const IdleWaiter > &idle_waiter, size_t minimum_uniform_alignment)
const std::shared_ptr< GlyphAtlas > & CreateOrGetGlyphAtlas(Context &context, HostBuffer &host_buffer, GlyphAtlas::Type type) const
void AddTextFrame(const std::shared_ptr< TextFrame > &frame, Rational scale, Point offset, const Matrix &transform, std::optional< GlyphProperties > properties)
static std::shared_ptr< RectanglePacker > Factory(int width, int height)
Return an empty packer with area specified by width and height.
The graphics context necessary to render text.
virtual std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, GlyphAtlas::Type type, HostBuffer &host_buffer, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::vector< std::shared_ptr< TextFrame > > &text_frames) const =0
static std::shared_ptr< TypographerContext > Make()
SkFont CreateTestFontOfSize(DlScalar scalar)
sk_sp< SkData > OpenFixtureAsSkData(const std::string &fixture_name)
Opens a fixture of the given file name and returns a Skia SkData holding its contents.
Definition testing.cc:63
bool NumberNear(double a, double b)
Definition assertions.h:12
TEST(FrameTimingsRecorderTest, RecordVsync)
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 disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
TEST_P(AiksTest, DrawAtlasNoColor)
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, HostBuffer &data_host_buffer, GlyphAtlas::Type type, Rational scale, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::shared_ptr< TextFrame > &frame)
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
sk_sp< SkFontMgr > GetDefaultFontManager(uint32_t font_initialization_data)
Definition platform.cc:17
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
static constexpr Color Red()
Definition color.h:272
static constexpr Color Blue()
Definition color.h:276
uint16_t index
Definition glyph.h:22
int16_t y() const
int16_t x() const
A 4x4 matrix using column-major storage.
Definition matrix.h:37
A font and a scale. Used as a key that represents a typeface within a glyph atlas.
A glyph and its subpixel position.