Flutter Impeller
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 
5 #include "flutter/display_list/testing/dl_test_snippets.h"
6 #include "flutter/testing/testing.h"
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 
26 namespace impeller {
27 namespace testing {
28 
31 
32 static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
33  Context& context,
34  const TypographerContext* typographer_context,
35  HostBuffer& 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, host_buffer,
42  atlas_context, {frame});
43 }
44 
45 static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
46  Context& context,
47  const TypographerContext* typographer_context,
48  HostBuffer& 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, host_buffer,
59  atlas_context, frames);
60 }
61 
62 TEST_P(TypographerTest, CanConvertTextBlob) {
63  SkFont font = flutter::testing::CreateTestFontOfSize(12);
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 
75 TEST_P(TypographerTest, CanCreateRenderContext) {
76  auto context = TypographerContextSkia::Make();
77  ASSERT_TRUE(context && context->IsValid());
78 }
79 
80 TEST_P(TypographerTest, CanCreateGlyphAtlas) {
81  auto context = TypographerContextSkia::Make();
82  auto atlas_context =
83  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
84  auto 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(), *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 
120 TEST_P(TypographerTest, LazyAtlasTracksColor) {
121  auto 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(), *host_buffer, GlyphAtlas::Type::kColorBitmap);
154 
155  auto bitmap_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
156  *GetContext(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap);
157 
158  ASSERT_FALSE(color_atlas == bitmap_atlas);
159 }
160 
161 TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
162  auto context = TypographerContextSkia::Make();
163  auto atlas_context =
164  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
165  auto 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(), *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 
183 TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
184  auto context = TypographerContextSkia::Make();
185  auto atlas_context =
186  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
187  auto 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(), *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(), *host_buffer,
207  atlas_context, MakeTextFrameFromTextBlobSkia(blob));
208  ASSERT_EQ(atlas, next_atlas);
209  ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas);
210 }
211 
212 TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
213  auto 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  *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 
262 TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
263  auto 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(), *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(), *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 
301 TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) {
302  auto 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.
320  auto frame = MakeTextFrameFromTextBlobSkia(
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(), *host_buffer,
332  atlas_context, {frame, frame_2}, properties);
333 
334  EXPECT_EQ(next_atlas->GetGlyphCount(), 2u);
335 }
336 
337 TEST_P(TypographerTest, GlyphColorIsIgnoredForNonEmojiFonts) {
338  auto 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 = {
357  GlyphProperties{},
358  GlyphProperties{},
359  };
360 
361  auto next_atlas =
362  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
364  atlas_context, {frame, frame_2}, properties);
365 
366  EXPECT_EQ(next_atlas->GetGlyphCount(), 1u);
367 }
368 
369 TEST_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 
413 TEST(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 
434 TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) {
435  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
436  GTEST_SKIP() << "Atlas growth isn't supported for OpenGLES currently.";
437  }
438 
439  auto 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(), *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, buffer.glyphs, count);
482  sk_font.getPos(buffer.glyphs, count, buffer.points(), {0, 0});
483  };
484 
485  SkFont sk_font = flutter::testing::CreateTestFontOfSize(50 + i);
486  add_char(sk_font, 'A');
487  add_char(sk_font_small, 'B');
488  auto blob = builder.make();
489 
490  atlas =
491  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
493  atlas_context, MakeTextFrameFromTextBlobSkia(blob));
494  ASSERT_TRUE(!!atlas);
495  EXPECT_EQ(atlas->GetTexture()->GetTextureDescriptor().size,
496  expected_sizes[i]);
497  }
498 
499  // The final atlas should contain both the "A" glyph (which was not present
500  // in the previous atlas) and the "B" glyph (which existed in the previous
501  // atlas).
502  ASSERT_EQ(atlas->GetGlyphCount(), 2u);
503 }
504 
505 TEST_P(TypographerTest, TextFrameInitialBoundsArePlaceholder) {
506  SkFont font = flutter::testing::CreateTestFontOfSize(12);
507  auto blob = SkTextBlob::MakeFromString(
508  "the quick brown fox jumped over the lazy dog.", font);
509  ASSERT_TRUE(blob);
510  auto frame = MakeTextFrameFromTextBlobSkia(blob);
511 
512  EXPECT_FALSE(frame->IsFrameComplete());
513 
514  auto context = TypographerContextSkia::Make();
515  auto atlas_context =
516  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
517  auto host_buffer = HostBuffer::Create(
518  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
519  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
520 
521  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
523  /*scale=*/Rational(1), atlas_context, frame);
524 
525  // The glyph position in the atlas was not known when this value
526  // was recorded. It is marked as a placeholder.
527  EXPECT_TRUE(frame->IsFrameComplete());
528  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
529 
530  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
532  /*scale=*/Rational(1), atlas_context, frame);
533 
534  // The second time the glyph is rendered, the bounds are correcly known.
535  EXPECT_TRUE(frame->IsFrameComplete());
536  EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
537 }
538 
539 TEST_P(TypographerTest, TextFrameInvalidationWithScale) {
540  SkFont font = flutter::testing::CreateTestFontOfSize(12);
541  auto blob = SkTextBlob::MakeFromString(
542  "the quick brown fox jumped over the lazy dog.", font);
543  ASSERT_TRUE(blob);
544  auto frame = MakeTextFrameFromTextBlobSkia(blob);
545 
546  EXPECT_FALSE(frame->IsFrameComplete());
547 
548  auto context = TypographerContextSkia::Make();
549  auto atlas_context =
550  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
551  auto host_buffer = HostBuffer::Create(
552  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
553  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
554 
555  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
557  /*scale=*/Rational(1), atlas_context, frame);
558 
559  // The glyph position in the atlas was not known when this value
560  // was recorded. It is marked as a placeholder.
561  EXPECT_TRUE(frame->IsFrameComplete());
562  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
563 
564  // Change the scale and the glyph data will still be a placeholder, as the
565  // old data is no longer valid.
566  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
568  /*scale=*/Rational(2), atlas_context, frame);
569 
570  // The second time the glyph is rendered, the bounds are correcly known.
571  EXPECT_TRUE(frame->IsFrameComplete());
572  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
573 }
574 
575 TEST_P(TypographerTest, TextFrameAtlasGenerationTracksState) {
576  SkFont font = flutter::testing::CreateTestFontOfSize(12);
577  auto blob = SkTextBlob::MakeFromString(
578  "the quick brown fox jumped over the lazy dog.", font);
579  ASSERT_TRUE(blob);
580  auto frame = MakeTextFrameFromTextBlobSkia(blob);
581 
582  EXPECT_FALSE(frame->IsFrameComplete());
583 
584  auto context = TypographerContextSkia::Make();
585  auto atlas_context =
586  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
587  auto host_buffer = HostBuffer::Create(
588  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
589  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
590 
591  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
593  /*scale=*/Rational(1), atlas_context, frame);
594 
595  // The glyph position in the atlas was not known when this value
596  // was recorded. It is marked as a placeholder.
597  EXPECT_TRUE(frame->IsFrameComplete());
598  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
599  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
600  // OpenGLES must always increase the atlas backend if the texture grows.
601  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
602  } else {
603  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
604  }
605 
606  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
608  /*scale=*/Rational(1), atlas_context, frame);
609 
610  // The second time the glyph is rendered, the bounds are correcly known.
611  EXPECT_TRUE(frame->IsFrameComplete());
612  EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
613  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
614  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
615  } else {
616  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
617  }
618 
619  // Force increase the generation.
620  atlas_context->GetGlyphAtlas()->SetAtlasGeneration(2u);
621  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
623  /*scale=*/Rational(1), atlas_context, frame);
624 
625  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 2u);
626 }
627 
628 TEST_P(TypographerTest, InvalidAtlasForcesRepopulation) {
629  SkFont font = flutter::testing::CreateTestFontOfSize(12);
630  auto blob = SkTextBlob::MakeFromString(
631  "the quick brown fox jumped over the lazy dog.", font);
632  ASSERT_TRUE(blob);
633  auto frame = MakeTextFrameFromTextBlobSkia(blob);
634 
635  EXPECT_FALSE(frame->IsFrameComplete());
636 
637  auto context = TypographerContextSkia::Make();
638  auto atlas_context =
639  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
640  auto host_buffer = HostBuffer::Create(
641  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
642  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
643 
644  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
646  /*scale=*/Rational(1), atlas_context, frame);
647 
648  // The glyph position in the atlas was not known when this value
649  // was recorded. It is marked as a placeholder.
650  EXPECT_TRUE(frame->IsFrameComplete());
651  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
652  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
653  // OpenGLES must always increase the atlas backend if the texture grows.
654  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
655  } else {
656  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
657  }
658 
659  auto second_context = TypographerContextSkia::Make();
660  auto second_atlas_context =
661  second_context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
662 
663  EXPECT_FALSE(second_atlas_context->GetGlyphAtlas()->IsValid());
664 
665  atlas = CreateGlyphAtlas(*GetContext(), second_context.get(), *host_buffer,
667  /*scale=*/Rational(1), second_atlas_context, frame);
668 
669  EXPECT_TRUE(second_atlas_context->GetGlyphAtlas()->IsValid());
670 }
671 
672 } // namespace testing
673 } // namespace impeller
674 
675 // 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:74
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)
Definition: host_buffer.cc:21
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()
bool NumberNear(double a, double b)
TEST(AllocationSizeTest, CanCreateTypedAllocations)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, HostBuffer &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)
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.