Flutter Impeller
typographer_context_skia.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 
6 
7 #include <cstddef>
8 #include <cstdint>
9 #include <memory>
10 #include <numeric>
11 #include <utility>
12 #include <vector>
13 
14 #include "flutter/fml/logging.h"
15 #include "flutter/fml/trace_event.h"
16 #include "fml/closure.h"
17 
21 #include "impeller/core/formats.h"
24 #include "impeller/geometry/rect.h"
25 #include "impeller/geometry/size.h"
35 
36 #include "third_party/abseil-cpp/absl/status/statusor.h"
37 #include "third_party/skia/include/core/SkBitmap.h"
38 #include "third_party/skia/include/core/SkBlendMode.h"
39 #include "third_party/skia/include/core/SkCanvas.h"
40 #include "third_party/skia/include/core/SkColor.h"
41 #include "third_party/skia/include/core/SkFont.h"
42 #include "third_party/skia/include/core/SkPaint.h"
43 #include "third_party/skia/include/core/SkSize.h"
44 #include "third_party/skia/include/core/SkSurface.h"
45 
46 namespace impeller {
47 
48 constexpr auto kPadding = 2;
49 
50 namespace {
51 SkPaint::Cap ToSkiaCap(Cap cap) {
52  switch (cap) {
53  case Cap::kButt:
54  return SkPaint::Cap::kButt_Cap;
55  case Cap::kRound:
56  return SkPaint::Cap::kRound_Cap;
57  case Cap::kSquare:
58  return SkPaint::Cap::kSquare_Cap;
59  }
60  FML_UNREACHABLE();
61 }
62 
63 SkPaint::Join ToSkiaJoin(Join join) {
64  switch (join) {
65  case Join::kMiter:
66  return SkPaint::Join::kMiter_Join;
67  case Join::kRound:
68  return SkPaint::Join::kRound_Join;
69  case Join::kBevel:
70  return SkPaint::Join::kBevel_Join;
71  }
72  FML_UNREACHABLE();
73 }
74 
75 bool HasLightGlyphs(const GlyphAtlas& atlas,
76  const std::vector<FontGlyphPair>& new_pairs,
77  size_t start_index,
78  size_t end_index) {
79  if (atlas.GetType() != GlyphAtlas::Type::kAlphaBitmap) {
80  return false;
81  }
82  for (size_t i = start_index; i < end_index; i++) {
83  if (new_pairs[i].glyph.properties.tone_or_color ==
85  return true;
86  }
87  }
88  return false;
89 }
90 
91 // Create an A8 bitmap from an color bitmap.
92 absl::StatusOr<SkBitmap> ToA8Bitmap(const SkBitmap& src) {
93  FML_DCHECK(src.colorType() == kRGBA_8888_SkColorType);
94 
95  SkBitmap a8_bitmap;
96  a8_bitmap.setInfo(SkImageInfo::MakeA8(src.width(), src.height()));
97  if (!a8_bitmap.tryAllocPixels()) {
98  return absl::Status(absl::StatusCode::kInternal,
99  "Failed to allocate pixels for A8 bitmap");
100  }
101  if (!src.readPixels(a8_bitmap.pixmap())) {
102  return absl::Status(absl::StatusCode::kInternal,
103  "Failed to read pixels into A8 bitmap");
104  }
105  return a8_bitmap;
106 }
107 
108 } // namespace
109 
110 std::shared_ptr<TypographerContext> TypographerContextSkia::Make() {
111  return std::make_shared<TypographerContextSkia>();
112 }
113 
115 
117 
118 std::shared_ptr<GlyphAtlasContext>
120  return std::make_shared<GlyphAtlasContext>(type);
121 }
122 
124  Size size,
125  bool support_light_glyphs) {
126  SkISize skia_size = {static_cast<int32_t>(size.width),
127  static_cast<int32_t>(size.height)};
128 
129  switch (atlas.GetType()) {
131  return support_light_glyphs
132  ? SkImageInfo::Make(skia_size, kRGBA_8888_SkColorType,
133  kPremul_SkAlphaType)
134  : SkImageInfo::MakeA8(skia_size);
136  return SkImageInfo::Make(skia_size, kRGBA_8888_SkColorType,
137  kPremul_SkAlphaType);
138  }
139  FML_UNREACHABLE();
140 }
141 
142 /// Append as many glyphs to the texture as will fit, and return the first index
143 /// of [extra_pairs] that did not fit.
144 static size_t AppendToExistingAtlas(
145  const std::shared_ptr<GlyphAtlas>& atlas,
146  const std::vector<FontGlyphPair>& extra_pairs,
147  std::vector<Rect>& glyph_positions,
148  const std::vector<Rect>& glyph_sizes,
149  ISize atlas_size,
150  int64_t height_adjustment,
151  const std::shared_ptr<RectanglePacker>& rect_packer) {
152  TRACE_EVENT0("impeller", __FUNCTION__);
153  if (!rect_packer || atlas_size.IsEmpty()) {
154  return 0;
155  }
156 
157  for (size_t i = 0; i < extra_pairs.size(); i++) {
158  ISize glyph_size = ISize::Ceil(glyph_sizes[i].GetSize());
159  IPoint16 location_in_atlas;
160  if (!rect_packer->AddRect(glyph_size.width + kPadding, //
161  glyph_size.height + kPadding, //
162  &location_in_atlas //
163  )) {
164  return i;
165  }
166  // Position the glyph in the center of the 1px padding.
167  glyph_positions.push_back(Rect::MakeXYWH(
168  location_in_atlas.x() + 1, //
169  location_in_atlas.y() + height_adjustment + 1, //
170  glyph_size.width, //
171  glyph_size.height //
172  ));
173  }
174 
175  return extra_pairs.size();
176 }
177 
178 static size_t PairsFitInAtlasOfSize(
179  const std::vector<FontGlyphPair>& pairs,
180  const ISize& atlas_size,
181  std::vector<Rect>& glyph_positions,
182  const std::vector<Rect>& glyph_sizes,
183  int64_t height_adjustment,
184  const std::shared_ptr<RectanglePacker>& rect_packer,
185  size_t start_index) {
186  FML_DCHECK(!atlas_size.IsEmpty());
187 
188  for (size_t i = start_index; i < pairs.size(); i++) {
189  ISize glyph_size = ISize::Ceil(glyph_sizes[i].GetSize());
190  IPoint16 location_in_atlas;
191  if (!rect_packer->AddRect(glyph_size.width + kPadding, //
192  glyph_size.height + kPadding, //
193  &location_in_atlas //
194  )) {
195  return i;
196  }
197  glyph_positions.push_back(Rect::MakeXYWH(
198  location_in_atlas.x() + 1, //
199  location_in_atlas.y() + height_adjustment + 1, //
200  glyph_size.width, //
201  glyph_size.height //
202  ));
203  }
204 
205  return pairs.size();
206 }
207 
209  const std::shared_ptr<GlyphAtlasContext>& atlas_context,
210  const std::vector<FontGlyphPair>& extra_pairs,
211  std::vector<Rect>& glyph_positions,
212  const std::vector<Rect>& glyph_sizes,
213  size_t glyph_index_start,
214  int64_t max_texture_height) {
215  // Because we can't grow the skyline packer horizontally, pick a reasonable
216  // large width for all atlases.
217  static constexpr int64_t kAtlasWidth = 4096;
218  static constexpr int64_t kMinAtlasHeight = 1024;
219 
220  ISize current_size = ISize(kAtlasWidth, kMinAtlasHeight);
221  if (atlas_context->GetAtlasSize().height > current_size.height) {
222  current_size.height = atlas_context->GetAtlasSize().height * 2;
223  }
224 
225  auto height_adjustment = atlas_context->GetAtlasSize().height;
226  while (current_size.height <= max_texture_height) {
227  std::shared_ptr<RectanglePacker> rect_packer;
228  if (atlas_context->GetRectPacker() || glyph_index_start) {
229  rect_packer = RectanglePacker::Factory(
230  kAtlasWidth,
231  current_size.height - atlas_context->GetAtlasSize().height);
232  } else {
233  rect_packer = RectanglePacker::Factory(kAtlasWidth, current_size.height);
234  }
235  glyph_positions.erase(glyph_positions.begin() + glyph_index_start,
236  glyph_positions.end());
237  atlas_context->UpdateRectPacker(rect_packer);
238  auto next_index = PairsFitInAtlasOfSize(
239  extra_pairs, current_size, glyph_positions, glyph_sizes,
240  height_adjustment, rect_packer, glyph_index_start);
241  if (next_index == extra_pairs.size()) {
242  return current_size;
243  }
244  current_size = ISize(current_size.width, current_size.height * 2);
245  }
246  return {};
247 }
248 
250  return Point((pos & 0xff) / 4.f, (pos >> 2 & 0xff) / 4.f);
251 }
252 
253 static void DrawGlyph(SkCanvas* canvas,
254  const SkPoint position,
255  const ScaledFont& scaled_font,
256  const SubpixelGlyph& glyph,
257  const Rect& scaled_bounds,
258  const GlyphProperties& prop) {
259  const auto& metrics = scaled_font.font.GetMetrics();
260  SkGlyphID glyph_id = glyph.glyph.index;
261 
262  SkFont sk_font(
264  metrics.point_size, metrics.scaleX, metrics.skewX);
265  sk_font.setEdging(SkFont::Edging::kAntiAlias);
266  sk_font.setHinting(SkFontHinting::kSlight);
267  sk_font.setEmbolden(metrics.embolden);
268  sk_font.setSubpixel(true);
269  sk_font.setSize(sk_font.getSize() * static_cast<Scalar>(scaled_font.scale));
270 
271  SkColor glyph_color;
273  glyph_color = SK_ColorBLACK;
274  } else if (prop.tone_or_color == GlyphProperties::kLightTone) {
275  glyph_color = SK_ColorWHITE;
276  } else {
277  FML_DCHECK(std::holds_alternative<Color>(prop.tone_or_color));
278  glyph_color = std::get<Color>(prop.tone_or_color).ToARGB();
279  }
280 
281  SkPaint glyph_paint;
282  glyph_paint.setColor(glyph_color);
283  glyph_paint.setBlendMode(SkBlendMode::kSrc);
284  if (prop.stroke.has_value()) {
285  auto stroke = prop.stroke;
286  glyph_paint.setStroke(true);
287  glyph_paint.setStrokeWidth(stroke->width *
288  static_cast<Scalar>(scaled_font.scale));
289  glyph_paint.setStrokeCap(ToSkiaCap(stroke->cap));
290  glyph_paint.setStrokeJoin(ToSkiaJoin(stroke->join));
291  glyph_paint.setStrokeMiter(stroke->miter_limit);
292  }
293  canvas->save();
294  Point subpixel_offset = SubpixelPositionToPoint(glyph.subpixel_offset);
295  canvas->translate(subpixel_offset.x, subpixel_offset.y);
296  // Draw a single glyph in the bounds
297  canvas->drawGlyphs({&glyph_id, 1u}, // glyphs
298  {&position, 1u}, // positions
299  SkPoint::Make(-scaled_bounds.GetLeft(),
300  -scaled_bounds.GetTop()), // origin
301  sk_font, // font
302  glyph_paint // paint
303  );
304  canvas->restore();
305 }
306 
307 /// @brief Batch render to a single surface.
308 ///
309 /// This is only safe for use when updating a fresh texture.
310 static bool BulkUpdateAtlasBitmap(const GlyphAtlas& atlas,
311  std::shared_ptr<BlitPass>& blit_pass,
312  HostBuffer& data_host_buffer,
313  const std::shared_ptr<Texture>& texture,
314  const std::vector<FontGlyphPair>& new_pairs,
315  size_t start_index,
316  size_t end_index) {
317  TRACE_EVENT0("impeller", __FUNCTION__);
318 
319  bool has_light_glyphs =
320  HasLightGlyphs(atlas, new_pairs, start_index, end_index);
321 
322  SkBitmap bitmap;
324  atlas, Size(texture->GetSize()), has_light_glyphs));
325  if (!bitmap.tryAllocPixels()) {
326  return false;
327  }
328 
329  auto surface = SkSurfaces::WrapPixels(bitmap.pixmap());
330  if (!surface) {
331  return false;
332  }
333  auto canvas = surface->getCanvas();
334  if (!canvas) {
335  return false;
336  }
337 
338  for (size_t i = start_index; i < end_index; i++) {
339  const FontGlyphPair& pair = new_pairs[i];
340  auto data = atlas.FindFontGlyphBounds(pair);
341  if (!data.has_value()) {
342  continue;
343  }
344  auto [pos, bounds, placeholder] = data.value();
345  FML_DCHECK(!placeholder);
346  Size size = pos.GetSize();
347  if (size.IsEmpty()) {
348  continue;
349  }
350 
351  DrawGlyph(canvas, SkPoint::Make(pos.GetLeft(), pos.GetTop()),
352  pair.scaled_font, pair.glyph, bounds, pair.glyph.properties);
353  }
354 
355  if (has_light_glyphs) {
356  auto a8_bitmap_status = ToA8Bitmap(bitmap);
357  if (!a8_bitmap_status.ok()) {
358  VALIDATION_LOG << a8_bitmap_status.status().message();
359  return false;
360  }
361  bitmap = a8_bitmap_status.value();
362  }
363 
364  // Writing to a malloc'd buffer and then copying to the staging buffers
365  // benchmarks as substantially faster on a number of Android devices.
366  BufferView buffer_view = data_host_buffer.Emplace(
367  bitmap.getAddr(0, 0),
368  texture->GetSize().Area() *
370  atlas.GetTexture()->GetTextureDescriptor().format),
371  data_host_buffer.GetMinimumUniformAlignment());
372 
373  return blit_pass->AddCopy(std::move(buffer_view), //
374  texture, //
375  IRect::MakeXYWH(0, 0, texture->GetSize().width,
376  texture->GetSize().height));
377 }
378 
379 static bool UpdateAtlasBitmap(const GlyphAtlas& atlas,
380  std::shared_ptr<BlitPass>& blit_pass,
381  HostBuffer& data_host_buffer,
382  const std::shared_ptr<Texture>& texture,
383  const std::vector<FontGlyphPair>& new_pairs,
384  size_t start_index,
385  size_t end_index) {
386  TRACE_EVENT0("impeller", __FUNCTION__);
387 
388  for (size_t i = start_index; i < end_index; i++) {
389  const FontGlyphPair& pair = new_pairs[i];
390  auto data = atlas.FindFontGlyphBounds(pair);
391  if (!data.has_value()) {
392  continue;
393  }
394  auto [pos, bounds, placeholder] = data.value();
395  FML_DCHECK(!placeholder);
396 
397  Size size = pos.GetSize();
398  if (size.IsEmpty()) {
399  continue;
400  }
401  // The uploaded bitmap is expanded by 1px of padding
402  // on each side.
403  size.width += 2;
404  size.height += 2;
405 
406  SkBitmap bitmap;
407  bool is_light_glyph =
409 
410  bitmap.setInfo(
411  TypographerContextSkia::GetImageInfo(atlas, size, is_light_glyph));
412  if (!bitmap.tryAllocPixels()) {
413  return false;
414  }
415 
416  auto surface = SkSurfaces::WrapPixels(bitmap.pixmap());
417  if (!surface) {
418  return false;
419  }
420  auto canvas = surface->getCanvas();
421  if (!canvas) {
422  return false;
423  }
424 
425  DrawGlyph(canvas, SkPoint::Make(1, 1), pair.scaled_font, pair.glyph, bounds,
426  pair.glyph.properties);
427 
428  if (is_light_glyph) {
429  auto a8_bitmap_status = ToA8Bitmap(bitmap);
430  if (!a8_bitmap_status.ok()) {
431  VALIDATION_LOG << a8_bitmap_status.status().message();
432  return false;
433  }
434  bitmap = a8_bitmap_status.value();
435  }
436 
437  // Writing to a malloc'd buffer and then copying to the staging buffers
438  // benchmarks as substantially faster on a number of Android devices.
439  BufferView buffer_view = data_host_buffer.Emplace(
440  bitmap.getAddr(0, 0),
442  atlas.GetTexture()->GetTextureDescriptor().format),
443  data_host_buffer.GetMinimumUniformAlignment());
444 
445  // convert_to_read is set to false so that the texture remains in a transfer
446  // dst layout until we finish writing to it below. This only has an impact
447  // on Vulkan where we are responsible for managing image layouts.
448  if (!blit_pass->AddCopy(std::move(buffer_view), //
449  texture, //
450  IRect::MakeXYWH(pos.GetLeft() - 1, pos.GetTop() - 1,
451  size.width, size.height), //
452  /*label=*/"", //
453  /*mip_level=*/0, //
454  /*slice=*/0, //
455  /*convert_to_read=*/false //
456  )) {
457  return false;
458  }
459  }
460  return blit_pass->ConvertTextureToShaderRead(texture);
461 }
462 
463 static Rect ComputeGlyphSize(const SkFont& font,
464  const SubpixelGlyph& glyph,
465  Scalar scale) {
466  SkRect scaled_bounds;
467  SkPaint glyph_paint;
468  if (glyph.properties.stroke.has_value()) {
469  glyph_paint.setStroke(true);
470  glyph_paint.setStrokeWidth(glyph.properties.stroke->width * scale);
471  glyph_paint.setStrokeCap(ToSkiaCap(glyph.properties.stroke->cap));
472  glyph_paint.setStrokeJoin(ToSkiaJoin(glyph.properties.stroke->join));
473  glyph_paint.setStrokeMiter(glyph.properties.stroke->miter_limit);
474  }
475  // Get bounds for a single glyph
476  font.getBounds({&glyph.glyph.index, 1}, {&scaled_bounds, 1}, &glyph_paint);
477 
478  // Expand the bounds of glyphs at subpixel offsets by 2 in the x direction.
479  Scalar adjustment = 0.0;
481  adjustment = 1.0;
482  }
483  return Rect::MakeLTRB(scaled_bounds.fLeft - adjustment, scaled_bounds.fTop,
484  scaled_bounds.fRight + adjustment,
485  scaled_bounds.fBottom);
486 };
487 
488 std::pair<std::vector<FontGlyphPair>, std::vector<Rect>>
489 TypographerContextSkia::CollectNewGlyphs(
490  const std::shared_ptr<GlyphAtlas>& atlas,
491  const std::vector<RenderableText>& renderable_texts) {
492  std::vector<FontGlyphPair> new_glyphs;
493  std::vector<Rect> glyph_sizes;
494  for (const auto& frame : renderable_texts) {
495  Rational rounded_scale = TextFrame::RoundScaledFontSize(
496  frame.origin_transform.GetMaxBasisLengthXY());
497  for (const auto& run : frame.text_frame->GetRuns()) {
498  auto metrics = run.GetFont().GetMetrics();
499 
500  ScaledFont scaled_font{.font = run.GetFont(), .scale = rounded_scale};
501 
502  FontGlyphAtlas* font_glyph_atlas =
503  atlas->GetOrCreateFontGlyphAtlas(scaled_font);
504  FML_DCHECK(!!font_glyph_atlas);
505 
506  SkFont sk_font(
507  TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(),
508  metrics.point_size, metrics.scaleX, metrics.skewX);
509  sk_font.setEdging(SkFont::Edging::kAntiAlias);
510  sk_font.setHinting(SkFontHinting::kSlight);
511  sk_font.setEmbolden(metrics.embolden);
512  // Rather than computing the bounds at the requested point size and
513  // scaling up the bounds, we scale up the font size and request the
514  // bounds. This seems to give more accurate bounds information.
515  sk_font.setSize(sk_font.getSize() *
516  static_cast<Scalar>(scaled_font.scale));
517  sk_font.setSubpixel(true);
518 
519  for (const auto& glyph_position : run.GetGlyphPositions()) {
521  glyph_position, scaled_font.font.GetAxisAlignment(),
522  frame.origin_transform);
523  SubpixelGlyph subpixel_glyph(glyph_position.glyph, subpixel,
524  frame.properties);
525  const auto& font_glyph_bounds =
526  font_glyph_atlas->FindGlyphBounds(subpixel_glyph);
527 
528  if (!font_glyph_bounds.has_value()) {
529  new_glyphs.push_back(FontGlyphPair{scaled_font, subpixel_glyph});
530  auto glyph_bounds = ComputeGlyphSize(
531  sk_font, subpixel_glyph, static_cast<Scalar>(scaled_font.scale));
532  glyph_sizes.push_back(glyph_bounds);
533 
534  auto frame_bounds = FrameBounds{
535  Rect::MakeLTRB(0, 0, 0, 0), //
536  glyph_bounds, //
537  /*placeholder=*/true //
538  };
539 
540  font_glyph_atlas->AppendGlyph(subpixel_glyph, frame_bounds);
541  }
542  }
543  }
544  }
545  return {std::move(new_glyphs), std::move(glyph_sizes)};
546 }
547 
548 std::shared_ptr<GlyphAtlas> TypographerContextSkia::CreateGlyphAtlas(
549  Context& context,
550  GlyphAtlas::Type type,
551  HostBuffer& data_host_buffer,
552  const std::shared_ptr<GlyphAtlasContext>& atlas_context,
553  const std::vector<RenderableText>& renderable_texts) const {
554  TRACE_EVENT0("impeller", __FUNCTION__);
555  if (!IsValid()) {
556  return nullptr;
557  }
558  std::shared_ptr<GlyphAtlas> last_atlas = atlas_context->GetGlyphAtlas();
559  FML_DCHECK(last_atlas->GetType() == type);
560 
561  if (renderable_texts.empty()) {
562  return last_atlas;
563  }
564 
565  // ---------------------------------------------------------------------------
566  // Step 1: Determine if the atlas type and font glyph pairs are compatible
567  // with the current atlas and reuse if possible. For each new font and
568  // glyph pair, compute the glyph size at scale.
569  // ---------------------------------------------------------------------------
570  auto [new_glyphs, glyph_sizes] =
571  CollectNewGlyphs(last_atlas, renderable_texts);
572  if (new_glyphs.size() == 0) {
573  return last_atlas;
574  }
575 
576  // ---------------------------------------------------------------------------
577  // Step 2: Determine if the additional missing glyphs can be appended to the
578  // existing bitmap without recreating the atlas.
579  // ---------------------------------------------------------------------------
580  std::vector<Rect> glyph_positions;
581  glyph_positions.reserve(new_glyphs.size());
582  size_t first_missing_index = 0;
583 
584  if (last_atlas->GetTexture()) {
585  // Append all glyphs that fit into the current atlas.
586  first_missing_index = AppendToExistingAtlas(
587  last_atlas, new_glyphs, glyph_positions, glyph_sizes,
588  atlas_context->GetAtlasSize(), atlas_context->GetHeightAdjustment(),
589  atlas_context->GetRectPacker());
590 
591  // ---------------------------------------------------------------------------
592  // Step 3a: Record the positions in the glyph atlas of the newly added
593  // glyphs.
594  // ---------------------------------------------------------------------------
595  for (size_t i = 0; i < first_missing_index; i++) {
596  last_atlas->AddTypefaceGlyphPositionAndBounds(
597  new_glyphs[i], glyph_positions[i], glyph_sizes[i]);
598  }
599 
600  std::shared_ptr<CommandBuffer> cmd_buffer = context.CreateCommandBuffer();
601  std::shared_ptr<BlitPass> blit_pass = cmd_buffer->CreateBlitPass();
602 
603  fml::ScopedCleanupClosure closure([&]() {
604  blit_pass->EncodeCommands();
605  if (!context.EnqueueCommandBuffer(std::move(cmd_buffer))) {
606  VALIDATION_LOG << "Failed to submit glyph atlas command buffer";
607  }
608  });
609 
610  // ---------------------------------------------------------------------------
611  // Step 4a: Draw new font-glyph pairs into the a host buffer and encode
612  // the uploads into the blit pass.
613  // ---------------------------------------------------------------------------
614  if (!UpdateAtlasBitmap(*last_atlas, blit_pass, data_host_buffer,
615  last_atlas->GetTexture(), new_glyphs, 0,
616  first_missing_index)) {
617  return nullptr;
618  }
619 
620  // If all glyphs fit, just return the old atlas.
621  if (first_missing_index == new_glyphs.size()) {
622  return last_atlas;
623  }
624  }
625 
626  int64_t height_adjustment = atlas_context->GetAtlasSize().height;
627  const int64_t max_texture_height =
628  context.GetResourceAllocator()->GetMaxTextureSizeSupported().height;
629 
630  // IF the current atlas size is as big as it can get, then "GC" and create an
631  // atlas with only the required glyphs. OpenGLES cannot reliably perform the
632  // blit required here, as 1) it requires attaching textures as read and write
633  // framebuffers which has substantially smaller size limits that max textures
634  // and 2) is missing a GLES 2.0 implementation and cap check.
635  bool blit_old_atlas = true;
636  std::shared_ptr<GlyphAtlas> new_atlas = last_atlas;
637  if (atlas_context->GetAtlasSize().height >= max_texture_height ||
638  context.GetBackendType() == Context::BackendType::kOpenGLES) {
639  blit_old_atlas = false;
640  new_atlas = std::make_shared<GlyphAtlas>(
641  type, /*initial_generation=*/last_atlas->GetAtlasGeneration() + 1);
642 
643  auto [update_glyphs, update_sizes] =
644  CollectNewGlyphs(new_atlas, renderable_texts);
645  new_glyphs = std::move(update_glyphs);
646  glyph_sizes = std::move(update_sizes);
647 
648  glyph_positions.clear();
649  glyph_positions.reserve(new_glyphs.size());
650  first_missing_index = 0;
651 
652  height_adjustment = 0;
653  atlas_context->UpdateRectPacker(nullptr);
654  atlas_context->UpdateGlyphAtlas(new_atlas, {0, 0}, 0);
655  }
656 
657  // A new glyph atlas must be created.
658  ISize atlas_size = ComputeNextAtlasSize(atlas_context, //
659  new_glyphs, //
660  glyph_positions, //
661  glyph_sizes, //
662  first_missing_index, //
663  max_texture_height //
664  );
665 
666  atlas_context->UpdateGlyphAtlas(new_atlas, atlas_size, height_adjustment);
667  if (atlas_size.IsEmpty()) {
668  return nullptr;
669  }
670  FML_DCHECK(new_glyphs.size() == glyph_positions.size());
671 
672  TextureDescriptor descriptor;
673  switch (type) {
675  descriptor.format =
676  context.GetCapabilities()->GetDefaultGlyphAtlasFormat();
677  break;
680  break;
681  }
682  descriptor.size = atlas_size;
684  descriptor.usage = TextureUsage::kShaderRead;
685  std::shared_ptr<Texture> new_texture =
686  context.GetResourceAllocator()->CreateTexture(descriptor);
687  if (!new_texture) {
688  return nullptr;
689  }
690 
691  new_texture->SetLabel("GlyphAtlas");
692 
693  std::shared_ptr<CommandBuffer> cmd_buffer = context.CreateCommandBuffer();
694  std::shared_ptr<BlitPass> blit_pass = cmd_buffer->CreateBlitPass();
695 
696  fml::ScopedCleanupClosure closure([&]() {
697  blit_pass->EncodeCommands();
698  if (!context.EnqueueCommandBuffer(std::move(cmd_buffer))) {
699  VALIDATION_LOG << "Failed to submit glyph atlas command buffer";
700  }
701  });
702 
703  // Now append all remaining glyphs. This should never have any missing data...
704  auto old_texture = new_atlas->GetTexture();
705  new_atlas->SetTexture(std::move(new_texture));
706 
707  // ---------------------------------------------------------------------------
708  // Step 3a: Record the positions in the glyph atlas of the newly added
709  // glyphs.
710  // ---------------------------------------------------------------------------
711  for (size_t i = first_missing_index; i < glyph_positions.size(); i++) {
712  new_atlas->AddTypefaceGlyphPositionAndBounds(
713  new_glyphs[i], glyph_positions[i], glyph_sizes[i]);
714  }
715 
716  // ---------------------------------------------------------------------------
717  // Step 4a: Draw new font-glyph pairs into the a host buffer and encode
718  // the uploads into the blit pass.
719  // ---------------------------------------------------------------------------
720  if (!BulkUpdateAtlasBitmap(*new_atlas, blit_pass, data_host_buffer,
721  new_atlas->GetTexture(), new_glyphs,
722  first_missing_index, new_glyphs.size())) {
723  return nullptr;
724  }
725 
726  // Blit the old texture to the top left of the new atlas.
727  if (blit_old_atlas && old_texture) {
728  blit_pass->AddCopy(old_texture, new_atlas->GetTexture(),
729  IRect::MakeSize(new_atlas->GetTexture()->GetSize()),
730  {0, 0});
731  }
732 
733  // ---------------------------------------------------------------------------
734  // Step 8b: Record the texture in the glyph atlas.
735  // ---------------------------------------------------------------------------
736 
737  return new_atlas;
738 }
739 
740 } // namespace impeller
static TypefaceSkia & Cast(Typeface &base)
Definition: backend_cast.h:13
To do anything rendering related with Impeller, you need a context.
Definition: context.h:69
const std::shared_ptr< Typeface > & GetTypeface() const
The typeface whose intrinsic properties this font modifies.
Definition: font.cc:27
const Metrics & GetMetrics() const
Definition: font.cc:45
A texture containing the bitmap representation of glyphs in different fonts along with the ability to...
Definition: glyph_atlas.h:37
std::optional< FrameBounds > FindFontGlyphBounds(const FontGlyphPair &pair) const
Find the location of a specific font-glyph pair in the atlas.
Definition: glyph_atlas.cc:87
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:41
Type GetType() const
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.cc:58
const std::shared_ptr< Texture > & GetTexture() const
Get the texture for the glyph atlas.
Definition: glyph_atlas.cc:62
BufferView Emplace(const BufferType &buffer, size_t alignment=0)
Emplace non-uniform data (like contiguous vertices) onto the host buffer.
Definition: host_buffer.h:92
size_t GetMinimumUniformAlignment() const
Retrieve the minimum uniform buffer alignment in bytes.
Definition: host_buffer.cc:241
static std::shared_ptr< RectanglePacker > Factory(int width, int height)
Return an empty packer with area specified by width and height.
static Rational RoundScaledFontSize(Scalar scale)
Definition: text_frame.cc:55
static SubpixelPosition ComputeSubpixelPosition(const TextRun::GlyphPosition &glyph_position, AxisAlignment alignment, const Matrix &transform)
Definition: text_frame.cc:94
const sk_sp< SkTypeface > & GetSkiaTypeface() const
std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, GlyphAtlas::Type type, HostBuffer &host_buffer, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::vector< RenderableText > &renderable_texts) const override
static SkImageInfo GetImageInfo(const GlyphAtlas &atlas, Size size, bool support_light_glyphs)
std::shared_ptr< GlyphAtlasContext > CreateGlyphAtlasContext(GlyphAtlas::Type type) const override
static std::shared_ptr< TypographerContext > Make()
constexpr size_t BytesPerPixelForPixelFormat(PixelFormat format)
Definition: formats.h:643
Join
An enum that describes ways to join two segments of a path.
static bool UpdateAtlasBitmap(const GlyphAtlas &atlas, std::shared_ptr< BlitPass > &blit_pass, HostBuffer &data_host_buffer, const std::shared_ptr< Texture > &texture, const std::vector< FontGlyphPair > &new_pairs, size_t start_index, size_t end_index)
static Rect ComputeGlyphSize(const SkFont &font, const SubpixelGlyph &glyph, Scalar scale)
float Scalar
Definition: scalar.h:19
static size_t PairsFitInAtlasOfSize(const std::vector< FontGlyphPair > &pairs, const ISize &atlas_size, std::vector< Rect > &glyph_positions, const std::vector< Rect > &glyph_sizes, int64_t height_adjustment, const std::shared_ptr< RectanglePacker > &rect_packer, size_t start_index)
static void DrawGlyph(SkCanvas *canvas, const SkPoint position, const ScaledFont &scaled_font, const SubpixelGlyph &glyph, const Rect &scaled_bounds, const GlyphProperties &prop)
static Point SubpixelPositionToPoint(SubpixelPosition pos)
TPoint< Scalar > Point
Definition: point.h:426
Cap
An enum that describes ways to decorate the end of a path contour.
static size_t AppendToExistingAtlas(const std::shared_ptr< GlyphAtlas > &atlas, const std::vector< FontGlyphPair > &extra_pairs, std::vector< Rect > &glyph_positions, const std::vector< Rect > &glyph_sizes, ISize atlas_size, int64_t height_adjustment, const std::shared_ptr< RectanglePacker > &rect_packer)
static ISize ComputeNextAtlasSize(const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::vector< FontGlyphPair > &extra_pairs, std::vector< Rect > &glyph_positions, const std::vector< Rect > &glyph_sizes, size_t glyph_index_start, int64_t max_texture_height)
static bool BulkUpdateAtlasBitmap(const GlyphAtlas &atlas, std::shared_ptr< BlitPass > &blit_pass, HostBuffer &data_host_buffer, const std::shared_ptr< Texture > &texture, const std::vector< FontGlyphPair > &new_pairs, size_t start_index, size_t end_index)
Batch render to a single surface.
TSize< Scalar > Size
Definition: size.h:159
ISize64 ISize
Definition: size.h:162
constexpr auto kPadding
std::shared_ptr< ContextGLES > context
A font along with a glyph in that font rendered at a particular scale and subpixel position.
uint16_t index
Definition: glyph.h:22
static constexpr ToneOrColor kLightTone
static constexpr ToneOrColor kDarkTone
std::optional< StrokeParameters > stroke
int16_t y() const
int16_t x() const
A font and a scale. Used as a key that represents a typeface within a glyph atlas.
A glyph and its subpixel position.
SubpixelPosition subpixel_offset
GlyphProperties properties
constexpr auto GetTop() const
Definition: rect.h:387
constexpr auto GetLeft() const
Definition: rect.h:385
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
constexpr Type Area() const
Definition: size.h:120
Type height
Definition: size.h:29
Type width
Definition: size.h:28
constexpr TSize Ceil() const
Definition: size.h:114
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: size.h:123
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
#define VALIDATION_LOG
Definition: validation.h:91