[Impeller] Reorganize the glyph atlas to improve efficiency when looking up runs of glyphs in the same font (#45191)

The atlas will now store a separate map of glyph positions for each font/scale pair.  This avoids the cost of repeated hashing and copying of font objects for each glyph in a text run.

See https://github.com/flutter/flutter/issues/133201
diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc
index a93967c..9495556 100644
--- a/impeller/entity/contents/text_contents.cc
+++ b/impeller/entity/contents/text_contents.cc
@@ -166,13 +166,17 @@
           const Font& font = run.GetFont();
           Scalar rounded_scale = TextFrame::RoundScaledFontSize(
               scale_, font.GetMetrics().point_size);
+          const FontGlyphAtlas* font_atlas =
+              atlas->GetFontGlyphAtlas(font, rounded_scale);
+          if (!font_atlas) {
+            VALIDATION_LOG << "Could not find font in the atlas.";
+            continue;
+          }
 
           for (const TextRun::GlyphPosition& glyph_position :
                run.GetGlyphPositions()) {
-            FontGlyphPair font_glyph_pair{font, glyph_position.glyph,
-                                          rounded_scale};
             std::optional<Rect> maybe_atlas_glyph_bounds =
-                atlas->FindFontGlyphBounds(font_glyph_pair);
+                font_atlas->FindGlyphBounds(glyph_position.glyph);
             if (!maybe_atlas_glyph_bounds.has_value()) {
               VALIDATION_LOG << "Could not find glyph position in the atlas.";
               continue;
diff --git a/impeller/typographer/backends/skia/typographer_context_skia.cc b/impeller/typographer/backends/skia/typographer_context_skia.cc
index 85bae56..9f8725a 100644
--- a/impeller/typographer/backends/skia/typographer_context_skia.cc
+++ b/impeller/typographer/backends/skia/typographer_context_skia.cc
@@ -4,6 +4,7 @@
 
 #include "impeller/typographer/backends/skia/typographer_context_skia.h"
 
+#include <numeric>
 #include <utility>
 
 #include "flutter/fml/logging.h"
@@ -21,9 +22,6 @@
 
 namespace impeller {
 
-using FontGlyphPairRefVector =
-    std::vector<std::reference_wrapper<const FontGlyphPair>>;
-
 // TODO(bdero): We might be able to remove this per-glyph padding if we fix
 //              the underlying causes of the overlap.
 //              https://github.com/flutter/flutter/issues/114563
@@ -43,7 +41,7 @@
 }
 
 static size_t PairsFitInAtlasOfSize(
-    const FontGlyphPair::Set& pairs,
+    const std::vector<FontGlyphPair>& pairs,
     const ISize& atlas_size,
     std::vector<Rect>& glyph_positions,
     const std::shared_ptr<RectanglePacker>& rect_packer) {
@@ -58,7 +56,8 @@
   for (auto it = pairs.begin(); it != pairs.end(); ++i, ++it) {
     const auto& pair = *it;
 
-    const auto glyph_size = ISize::Ceil(pair.glyph.bounds.size * pair.scale);
+    const auto glyph_size =
+        ISize::Ceil(pair.glyph.bounds.size * pair.scaled_font.scale);
     IPoint16 location_in_atlas;
     if (!rect_packer->addRect(glyph_size.width + kPadding,   //
                               glyph_size.height + kPadding,  //
@@ -78,7 +77,7 @@
 
 static bool CanAppendToExistingAtlas(
     const std::shared_ptr<GlyphAtlas>& atlas,
-    const FontGlyphPairRefVector& extra_pairs,
+    const std::vector<FontGlyphPair>& extra_pairs,
     std::vector<Rect>& glyph_positions,
     ISize atlas_size,
     const std::shared_ptr<RectanglePacker>& rect_packer) {
@@ -95,7 +94,8 @@
   for (size_t i = 0; i < extra_pairs.size(); i++) {
     const FontGlyphPair& pair = extra_pairs[i];
 
-    const auto glyph_size = ISize::Ceil(pair.glyph.bounds.size * pair.scale);
+    const auto glyph_size =
+        ISize::Ceil(pair.glyph.bounds.size * pair.scaled_font.scale);
     IPoint16 location_in_atlas;
     if (!rect_packer->addRect(glyph_size.width + kPadding,   //
                               glyph_size.height + kPadding,  //
@@ -114,7 +114,7 @@
 }
 
 static ISize OptimumAtlasSizeForFontGlyphPairs(
-    const FontGlyphPair::Set& pairs,
+    const std::vector<FontGlyphPair>& pairs,
     std::vector<Rect>& glyph_positions,
     const std::shared_ptr<GlyphAtlasContext>& atlas_context,
     GlyphAtlas::Type type) {
@@ -153,16 +153,17 @@
 }
 
 static void DrawGlyph(SkCanvas* canvas,
-                      const FontGlyphPair& font_glyph,
+                      const ScaledFont& scaled_font,
+                      const Glyph& glyph,
                       const Rect& location,
                       bool has_color) {
-  const auto& metrics = font_glyph.font.GetMetrics();
-  const auto position = SkPoint::Make(location.origin.x / font_glyph.scale,
-                                      location.origin.y / font_glyph.scale);
-  SkGlyphID glyph_id = font_glyph.glyph.index;
+  const auto& metrics = scaled_font.font.GetMetrics();
+  const auto position = SkPoint::Make(location.origin.x / scaled_font.scale,
+                                      location.origin.y / scaled_font.scale);
+  SkGlyphID glyph_id = glyph.index;
 
   SkFont sk_font(
-      TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
+      TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(),
       metrics.point_size, metrics.scaleX, metrics.skewX);
   sk_font.setEdging(SkFont::Edging::kAntiAlias);
   sk_font.setHinting(SkFontHinting::kSlight);
@@ -173,21 +174,20 @@
   SkPaint glyph_paint;
   glyph_paint.setColor(glyph_color);
   canvas->resetMatrix();
-  canvas->scale(font_glyph.scale, font_glyph.scale);
-  canvas->drawGlyphs(
-      1u,         // count
-      &glyph_id,  // glyphs
-      &position,  // positions
-      SkPoint::Make(-font_glyph.glyph.bounds.GetLeft(),
-                    -font_glyph.glyph.bounds.GetTop()),  // origin
-      sk_font,                                           // font
-      glyph_paint                                        // paint
+  canvas->scale(scaled_font.scale, scaled_font.scale);
+  canvas->drawGlyphs(1u,         // count
+                     &glyph_id,  // glyphs
+                     &position,  // positions
+                     SkPoint::Make(-glyph.bounds.GetLeft(),
+                                   -glyph.bounds.GetTop()),  // origin
+                     sk_font,                                // font
+                     glyph_paint                             // paint
   );
 }
 
 static bool UpdateAtlasBitmap(const GlyphAtlas& atlas,
                               const std::shared_ptr<SkBitmap>& bitmap,
-                              const FontGlyphPairRefVector& new_pairs) {
+                              const std::vector<FontGlyphPair>& new_pairs) {
   TRACE_EVENT0("impeller", __FUNCTION__);
   FML_DCHECK(bitmap != nullptr);
 
@@ -207,7 +207,7 @@
     if (!pos.has_value()) {
       continue;
     }
-    DrawGlyph(canvas, pair, pos.value(), has_color);
+    DrawGlyph(canvas, pair.scaled_font, pair.glyph, pos.value(), has_color);
   }
   return true;
 }
@@ -243,9 +243,10 @@
 
   bool has_color = atlas.GetType() == GlyphAtlas::Type::kColorBitmap;
 
-  atlas.IterateGlyphs([canvas, has_color](const FontGlyphPair& font_glyph,
+  atlas.IterateGlyphs([canvas, has_color](const ScaledFont& scaled_font,
+                                          const Glyph& glyph,
                                           const Rect& location) -> bool {
-    DrawGlyph(canvas, font_glyph, location, has_color);
+    DrawGlyph(canvas, scaled_font, glyph, location, has_color);
     return true;
   });
 
@@ -313,7 +314,7 @@
     Context& context,
     GlyphAtlas::Type type,
     std::shared_ptr<GlyphAtlasContext> atlas_context,
-    const FontGlyphPair::Set& font_glyph_pairs) const {
+    const FontGlyphMap& font_glyph_map) const {
   TRACE_EVENT0("impeller", __FUNCTION__);
   if (!IsValid()) {
     return nullptr;
@@ -321,7 +322,7 @@
   auto& atlas_context_skia = GlyphAtlasContextSkia::Cast(*atlas_context);
   std::shared_ptr<GlyphAtlas> last_atlas = atlas_context->GetGlyphAtlas();
 
-  if (font_glyph_pairs.empty()) {
+  if (font_glyph_map.empty()) {
     return last_atlas;
   }
 
@@ -329,10 +330,21 @@
   // Step 1: Determine if the atlas type and font glyph pairs are compatible
   //         with the current atlas and reuse if possible.
   // ---------------------------------------------------------------------------
-  FontGlyphPairRefVector new_glyphs;
-  for (const FontGlyphPair& pair : font_glyph_pairs) {
-    if (!last_atlas->FindFontGlyphBounds(pair).has_value()) {
-      new_glyphs.push_back(pair);
+  std::vector<FontGlyphPair> new_glyphs;
+  for (const auto& font_value : font_glyph_map) {
+    const ScaledFont& scaled_font = font_value.first;
+    const FontGlyphAtlas* font_glyph_atlas =
+        last_atlas->GetFontGlyphAtlas(scaled_font.font, scaled_font.scale);
+    if (font_glyph_atlas) {
+      for (const Glyph& glyph : font_value.second) {
+        if (!font_glyph_atlas->FindGlyphBounds(glyph)) {
+          new_glyphs.emplace_back(scaled_font, glyph);
+        }
+      }
+    } else {
+      for (const Glyph& glyph : font_value.second) {
+        new_glyphs.emplace_back(scaled_font, glyph);
+      }
     }
   }
   if (last_atlas->GetType() == type && new_glyphs.size() == 0) {
@@ -381,6 +393,16 @@
   // ---------------------------------------------------------------------------
   // Step 3b: Get the optimum size of the texture atlas.
   // ---------------------------------------------------------------------------
+  std::vector<FontGlyphPair> font_glyph_pairs;
+  font_glyph_pairs.reserve(std::accumulate(
+      font_glyph_map.begin(), font_glyph_map.end(), 0,
+      [](const int a, const auto& b) { return a + b.second.size(); }));
+  for (const auto& font_value : font_glyph_map) {
+    const ScaledFont& scaled_font = font_value.first;
+    for (const Glyph& glyph : font_value.second) {
+      font_glyph_pairs.push_back({scaled_font, glyph});
+    }
+  }
   auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
   auto atlas_size = OptimumAtlasSizeForFontGlyphPairs(
       font_glyph_pairs, glyph_positions, atlas_context, type);
diff --git a/impeller/typographer/backends/skia/typographer_context_skia.h b/impeller/typographer/backends/skia/typographer_context_skia.h
index 3c1ebf5..f54592e 100644
--- a/impeller/typographer/backends/skia/typographer_context_skia.h
+++ b/impeller/typographer/backends/skia/typographer_context_skia.h
@@ -25,7 +25,7 @@
       Context& context,
       GlyphAtlas::Type type,
       std::shared_ptr<GlyphAtlasContext> atlas_context,
-      const FontGlyphPair::Set& font_glyph_pairs) const override;
+      const FontGlyphMap& font_glyph_map) const override;
 
  private:
   FML_DISALLOW_COPY_AND_ASSIGN(TypographerContextSkia);
diff --git a/impeller/typographer/backends/stb/typographer_context_stb.cc b/impeller/typographer/backends/stb/typographer_context_stb.cc
index 5efdcf4..5669d7e 100644
--- a/impeller/typographer/backends/stb/typographer_context_stb.cc
+++ b/impeller/typographer/backends/stb/typographer_context_stb.cc
@@ -4,6 +4,7 @@
 
 #include "impeller/typographer/backends/stb/typographer_context_stb.h"
 
+#include <numeric>
 #include <utility>
 
 #include "flutter/fml/logging.h"
@@ -23,9 +24,6 @@
 
 namespace impeller {
 
-using FontGlyphPairRefVector =
-    std::vector<std::reference_wrapper<const FontGlyphPair>>;
-
 constexpr size_t kPadding = 1;
 
 std::unique_ptr<TypographerContext> TypographerContextSTB::Make() {
@@ -44,7 +42,7 @@
 // Function returns the count of "remaining pairs" not packed into rect of given
 // size.
 static size_t PairsFitInAtlasOfSize(
-    const FontGlyphPair::Set& pairs,
+    const std::vector<FontGlyphPair>& pairs,
     const ISize& atlas_size,
     std::vector<Rect>& glyph_positions,
     const std::shared_ptr<RectanglePacker>& rect_packer) {
@@ -58,15 +56,16 @@
   size_t i = 0;
   for (auto it = pairs.begin(); it != pairs.end(); ++i, ++it) {
     const auto& pair = *it;
+    const Font& font = pair.scaled_font.font;
 
     // We downcast to the correct typeface type to access `stb` specific
     // methods.
     std::shared_ptr<TypefaceSTB> typeface_stb =
-        std::reinterpret_pointer_cast<TypefaceSTB>(pair.font.GetTypeface());
+        std::reinterpret_pointer_cast<TypefaceSTB>(font.GetTypeface());
     // Conversion factor to scale font size in Points to pixels.
     // Note this assumes typical DPI.
     float text_size_pixels =
-        pair.font.GetMetrics().point_size * TypefaceSTB::kPointsToPixels;
+        font.GetMetrics().point_size * TypefaceSTB::kPointsToPixels;
 
     ISize glyph_size;
     {
@@ -100,7 +99,7 @@
 
 static bool CanAppendToExistingAtlas(
     const std::shared_ptr<GlyphAtlas>& atlas,
-    const FontGlyphPairRefVector& extra_pairs,
+    const std::vector<FontGlyphPair>& extra_pairs,
     std::vector<Rect>& glyph_positions,
     ISize atlas_size,
     const std::shared_ptr<RectanglePacker>& rect_packer) {
@@ -116,14 +115,15 @@
   glyph_positions.reserve(extra_pairs.size());
   for (size_t i = 0; i < extra_pairs.size(); i++) {
     const FontGlyphPair& pair = extra_pairs[i];
+    const Font& font = pair.scaled_font.font;
 
     // We downcast to the correct typeface type to access `stb` specific methods
     std::shared_ptr<TypefaceSTB> typeface_stb =
-        std::reinterpret_pointer_cast<TypefaceSTB>(pair.font.GetTypeface());
+        std::reinterpret_pointer_cast<TypefaceSTB>(font.GetTypeface());
     // Conversion factor to scale font size in Points to pixels.
     // Note this assumes typical DPI.
     float text_size_pixels =
-        pair.font.GetMetrics().point_size * TypefaceSTB::kPointsToPixels;
+        font.GetMetrics().point_size * TypefaceSTB::kPointsToPixels;
 
     ISize glyph_size;
     {
@@ -157,7 +157,7 @@
 }
 
 static ISize OptimumAtlasSizeForFontGlyphPairs(
-    const FontGlyphPair::Set& pairs,
+    const std::vector<FontGlyphPair>& pairs,
     std::vector<Rect>& glyph_positions,
     const std::shared_ptr<GlyphAtlasContext>& atlas_context,
     GlyphAtlas::Type type) {
@@ -196,13 +196,13 @@
 }
 
 static void DrawGlyph(BitmapSTB* bitmap,
-                      const FontGlyphPair& font_glyph,
+                      const ScaledFont& scaled_font,
+                      const Glyph& glyph,
                       const Rect& location,
                       bool has_color) {
-  const auto& metrics = font_glyph.font.GetMetrics();
+  const auto& metrics = scaled_font.font.GetMetrics();
 
-  const impeller::Font& font = font_glyph.font;
-  const impeller::Glyph& glyph = font_glyph.glyph;
+  const impeller::Font& font = scaled_font.font;
   auto typeface = font.GetTypeface();
   // We downcast to the correct typeface type to access `stb` specific methods
   std::shared_ptr<TypefaceSTB> typeface_stb =
@@ -272,7 +272,7 @@
 
 static bool UpdateAtlasBitmap(const GlyphAtlas& atlas,
                               const std::shared_ptr<BitmapSTB>& bitmap,
-                              const FontGlyphPairRefVector& new_pairs) {
+                              const std::vector<FontGlyphPair>& new_pairs) {
   TRACE_EVENT0("impeller", __FUNCTION__);
   FML_DCHECK(bitmap != nullptr);
 
@@ -283,7 +283,8 @@
     if (!pos.has_value()) {
       continue;
     }
-    DrawGlyph(bitmap.get(), pair, pos.value(), has_color);
+    DrawGlyph(bitmap.get(), pair.scaled_font, pair.glyph, pos.value(),
+              has_color);
   }
   return true;
 }
@@ -302,9 +303,10 @@
 
   bool has_color = atlas.GetType() == GlyphAtlas::Type::kColorBitmap;
 
-  atlas.IterateGlyphs([&bitmap, has_color](const FontGlyphPair& font_glyph,
+  atlas.IterateGlyphs([&bitmap, has_color](const ScaledFont& scaled_font,
+                                           const Glyph& glyph,
                                            const Rect& location) -> bool {
-    DrawGlyph(bitmap.get(), font_glyph, location, has_color);
+    DrawGlyph(bitmap.get(), scaled_font, glyph, location, has_color);
     return true;
   });
 
@@ -375,7 +377,7 @@
     Context& context,
     GlyphAtlas::Type type,
     std::shared_ptr<GlyphAtlasContext> atlas_context,
-    const FontGlyphPair::Set& font_glyph_pairs) const {
+    const FontGlyphMap& font_glyph_map) const {
   TRACE_EVENT0("impeller", __FUNCTION__);
   if (!IsValid()) {
     return nullptr;
@@ -383,7 +385,7 @@
   auto& atlas_context_stb = GlyphAtlasContextSTB::Cast(*atlas_context);
   std::shared_ptr<GlyphAtlas> last_atlas = atlas_context->GetGlyphAtlas();
 
-  if (font_glyph_pairs.empty()) {
+  if (font_glyph_map.empty()) {
     return last_atlas;
   }
 
@@ -391,10 +393,21 @@
   // Step 1: Determine if the atlas type and font glyph pairs are compatible
   //         with the current atlas and reuse if possible.
   // ---------------------------------------------------------------------------
-  FontGlyphPairRefVector new_glyphs;
-  for (const FontGlyphPair& pair : font_glyph_pairs) {
-    if (!last_atlas->FindFontGlyphBounds(pair).has_value()) {
-      new_glyphs.push_back(pair);
+  std::vector<FontGlyphPair> new_glyphs;
+  for (const auto& font_value : font_glyph_map) {
+    const ScaledFont& scaled_font = font_value.first;
+    const FontGlyphAtlas* font_glyph_atlas =
+        last_atlas->GetFontGlyphAtlas(scaled_font.font, scaled_font.scale);
+    if (font_glyph_atlas) {
+      for (const Glyph& glyph : font_value.second) {
+        if (!font_glyph_atlas->FindGlyphBounds(glyph)) {
+          new_glyphs.emplace_back(scaled_font, glyph);
+        }
+      }
+    } else {
+      for (const Glyph& glyph : font_value.second) {
+        new_glyphs.emplace_back(scaled_font, glyph);
+      }
     }
   }
   if (last_atlas->GetType() == type && new_glyphs.size() == 0) {
@@ -444,6 +457,16 @@
   // ---------------------------------------------------------------------------
   // Step 3b: Get the optimum size of the texture atlas.
   // ---------------------------------------------------------------------------
+  std::vector<FontGlyphPair> font_glyph_pairs;
+  font_glyph_pairs.reserve(std::accumulate(
+      font_glyph_map.begin(), font_glyph_map.end(), 0,
+      [](const int a, const auto& b) { return a + b.second.size(); }));
+  for (const auto& font_value : font_glyph_map) {
+    const ScaledFont& scaled_font = font_value.first;
+    for (const Glyph& glyph : font_value.second) {
+      font_glyph_pairs.push_back({scaled_font, glyph});
+    }
+  }
   auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
   auto atlas_size = OptimumAtlasSizeForFontGlyphPairs(
       font_glyph_pairs, glyph_positions, atlas_context, type);
diff --git a/impeller/typographer/backends/stb/typographer_context_stb.h b/impeller/typographer/backends/stb/typographer_context_stb.h
index 5496931..4254c01 100644
--- a/impeller/typographer/backends/stb/typographer_context_stb.h
+++ b/impeller/typographer/backends/stb/typographer_context_stb.h
@@ -27,7 +27,7 @@
       Context& context,
       GlyphAtlas::Type type,
       std::shared_ptr<GlyphAtlasContext> atlas_context,
-      const FontGlyphPair::Set& font_glyph_pairs) const override;
+      const FontGlyphMap& font_glyph_map) const override;
 
  private:
   FML_DISALLOW_COPY_AND_ASSIGN(TypographerContextSTB);
diff --git a/impeller/typographer/font_glyph_pair.h b/impeller/typographer/font_glyph_pair.h
index 16eaecd..60be5b8 100644
--- a/impeller/typographer/font_glyph_pair.h
+++ b/impeller/typographer/font_glyph_pair.h
@@ -16,43 +16,40 @@
 namespace impeller {
 
 //------------------------------------------------------------------------------
+/// @brief      A font and a scale.  Used as a key that represents a typeface
+///             within a glyph atlas.
+///
+struct ScaledFont {
+  Font font;
+  Scalar scale;
+};
+
+using FontGlyphMap = std::unordered_map<ScaledFont, std::unordered_set<Glyph>>;
+
+//------------------------------------------------------------------------------
 /// @brief      A font along with a glyph in that font rendered at a particular
-///             scale. Used in glyph atlases as keys.
+///             scale.
 ///
 struct FontGlyphPair {
-  struct Hash;
-  struct Equal;
-
-  using Set = std::unordered_set<FontGlyphPair, Hash, Equal>;
-
-  Font font;
-  Glyph glyph;
-  Scalar scale;
-
-  struct Hash {
-    std::size_t operator()(const FontGlyphPair& p) const {
-      static_assert(sizeof(p.glyph.index) == 2);
-      static_assert(sizeof(p.glyph.type) == 1);
-      size_t index = p.glyph.index;
-      size_t type = static_cast<size_t>(p.glyph.type);
-      // By packaging multiple values in a single size_t the hash function is
-      // more efficient without losing entropy.
-      if (sizeof(size_t) == 8 && sizeof(Scalar) == 4) {
-        const float* fScale = &p.scale;
-        size_t nScale = *reinterpret_cast<const uint32_t*>(fScale);
-        size_t index_type_scale = nScale << 32 | index << 16 | type;
-        return fml::HashCombine(p.font.GetHash(), index_type_scale);
-      }
-      size_t index_type = index << 16 | type;
-      return fml::HashCombine(p.font.GetHash(), index_type, p.scale);
-    }
-  };
-  struct Equal {
-    bool operator()(const FontGlyphPair& lhs, const FontGlyphPair& rhs) const {
-      return lhs.font.IsEqual(rhs.font) && lhs.glyph.index == rhs.glyph.index &&
-             lhs.glyph.type == rhs.glyph.type && lhs.scale == rhs.scale;
-    }
-  };
+  FontGlyphPair(const ScaledFont& sf, const Glyph& g)
+      : scaled_font(sf), glyph(g) {}
+  const ScaledFont& scaled_font;
+  const Glyph& glyph;
 };
 
 }  // namespace impeller
+
+template <>
+struct std::hash<impeller::ScaledFont> {
+  constexpr std::size_t operator()(const impeller::ScaledFont& sf) const {
+    return fml::HashCombine(sf.font.GetHash(), sf.scale);
+  }
+};
+
+template <>
+struct std::equal_to<impeller::ScaledFont> {
+  constexpr bool operator()(const impeller::ScaledFont& lhs,
+                            const impeller::ScaledFont& rhs) const {
+    return lhs.font.IsEqual(rhs.font) && lhs.scale == rhs.scale;
+  }
+};
diff --git a/impeller/typographer/glyph.h b/impeller/typographer/glyph.h
index d379756..c9aad01 100644
--- a/impeller/typographer/glyph.h
+++ b/impeller/typographer/glyph.h
@@ -48,7 +48,9 @@
 template <>
 struct std::hash<impeller::Glyph> {
   constexpr std::size_t operator()(const impeller::Glyph& g) const {
-    return fml::HashCombine(g.index, g.type);
+    static_assert(sizeof(g.index) == 2);
+    static_assert(sizeof(g.type) == 1);
+    return (static_cast<size_t>(g.type) << 16) | g.index;
   }
 };
 
diff --git a/impeller/typographer/glyph_atlas.cc b/impeller/typographer/glyph_atlas.cc
index 21a30ad..1f9bfaf 100644
--- a/impeller/typographer/glyph_atlas.cc
+++ b/impeller/typographer/glyph_atlas.cc
@@ -4,6 +4,7 @@
 
 #include "impeller/typographer/glyph_atlas.h"
 
+#include <numeric>
 #include <utility>
 
 namespace impeller {
@@ -59,37 +60,60 @@
 
 void GlyphAtlas::AddTypefaceGlyphPosition(const FontGlyphPair& pair,
                                           Rect rect) {
-  positions_[pair] = rect;
+  font_atlas_map_[pair.scaled_font].positions_[pair.glyph] = rect;
 }
 
 std::optional<Rect> GlyphAtlas::FindFontGlyphBounds(
     const FontGlyphPair& pair) const {
-  const auto& found = positions_.find(pair);
-  if (found == positions_.end()) {
+  const auto& found = font_atlas_map_.find(pair.scaled_font);
+  if (found == font_atlas_map_.end()) {
     return std::nullopt;
   }
-  return found->second;
+  return found->second.FindGlyphBounds(pair.glyph);
+}
+
+const FontGlyphAtlas* GlyphAtlas::GetFontGlyphAtlas(const Font& font,
+                                                    Scalar scale) const {
+  const auto& found = font_atlas_map_.find({font, scale});
+  if (found == font_atlas_map_.end()) {
+    return nullptr;
+  }
+  return &found->second;
 }
 
 size_t GlyphAtlas::GetGlyphCount() const {
-  return positions_.size();
+  return std::accumulate(font_atlas_map_.begin(), font_atlas_map_.end(), 0,
+                         [](const int a, const auto& b) {
+                           return a + b.second.positions_.size();
+                         });
 }
 
 size_t GlyphAtlas::IterateGlyphs(
-    const std::function<bool(const FontGlyphPair& pair, const Rect& rect)>&
-        iterator) const {
+    const std::function<bool(const ScaledFont& scaled_font,
+                             const Glyph& glyph,
+                             const Rect& rect)>& iterator) const {
   if (!iterator) {
     return 0u;
   }
 
   size_t count = 0u;
-  for (const auto& position : positions_) {
-    count++;
-    if (!iterator(position.first, position.second)) {
-      return count;
+  for (const auto& font_value : font_atlas_map_) {
+    for (const auto& glyph_value : font_value.second.positions_) {
+      count++;
+      if (!iterator(font_value.first, glyph_value.first, glyph_value.second)) {
+        return count;
+      }
     }
   }
   return count;
 }
 
+std::optional<Rect> FontGlyphAtlas::FindGlyphBounds(const Glyph& glyph) const {
+  const auto& found = positions_.find(glyph);
+  if (found == positions_.end()) {
+    return std::nullopt;
+  }
+  return found->second;
+}
+
 }  // namespace impeller
diff --git a/impeller/typographer/glyph_atlas.h b/impeller/typographer/glyph_atlas.h
index c7d4ea4..03d428a 100644
--- a/impeller/typographer/glyph_atlas.h
+++ b/impeller/typographer/glyph_atlas.h
@@ -18,6 +18,8 @@
 
 namespace impeller {
 
+class FontGlyphAtlas;
+
 //------------------------------------------------------------------------------
 /// @brief      A texture containing the bitmap representation of glyphs in
 ///             different fonts along with the ability to query the location of
@@ -97,8 +99,9 @@
   /// @return     The number of glyphs iterated over.
   ///
   size_t IterateGlyphs(
-      const std::function<bool(const FontGlyphPair& pair, const Rect& rect)>&
-          iterator) const;
+      const std::function<bool(const ScaledFont& scaled_font,
+                               const Glyph& glyph,
+                               const Rect& rect)>& iterator) const;
 
   //----------------------------------------------------------------------------
   /// @brief      Find the location of a specific font-glyph pair in the atlas.
@@ -106,19 +109,29 @@
   /// @param[in]  pair  The font-glyph pair
   ///
   /// @return     The location of the font-glyph pair in the atlas.
-  ///             `std::nullopt` of the pair in not in the atlas.
+  ///             `std::nullopt` if the pair is not in the atlas.
   ///
   std::optional<Rect> FindFontGlyphBounds(const FontGlyphPair& pair) const;
 
+  //----------------------------------------------------------------------------
+  /// @brief      Obtain an interface for querying the location of glyphs in the
+  ///             atlas for the given font and scale.  This provides a more
+  ///             efficient way to look up a run of glyphs in the same font.
+  ///
+  /// @param[in]  font  The font
+  /// @param[in]  scale The scale
+  ///
+  /// @return     A pointer to a FontGlyphAtlas, or nullptr if the font and
+  ///             scale are not available in the atlas.  The pointer is only
+  ///             valid for the lifetime of the GlyphAtlas.
+  ///
+  const FontGlyphAtlas* GetFontGlyphAtlas(const Font& font, Scalar scale) const;
+
  private:
   const Type type_;
   std::shared_ptr<Texture> texture_;
 
-  std::unordered_map<FontGlyphPair,
-                     Rect,
-                     FontGlyphPair::Hash,
-                     FontGlyphPair::Equal>
-      positions_;
+  std::unordered_map<ScaledFont, FontGlyphAtlas> font_atlas_map_;
 
   FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlas);
 };
@@ -159,4 +172,29 @@
   FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlasContext);
 };
 
+//------------------------------------------------------------------------------
+/// @brief      An object that can look up glyph locations within the GlyphAtlas
+///             for a particular typeface.
+///
+class FontGlyphAtlas {
+ public:
+  FontGlyphAtlas() = default;
+
+  //----------------------------------------------------------------------------
+  /// @brief      Find the location of a glyph in the atlas.
+  ///
+  /// @param[in]  glyph The glyph
+  ///
+  /// @return     The location of the glyph in the atlas.
+  ///             `std::nullopt` if the glyph is not in the atlas.
+  ///
+  std::optional<Rect> FindGlyphBounds(const Glyph& glyph) const;
+
+ private:
+  friend class GlyphAtlas;
+  std::unordered_map<Glyph, Rect> positions_;
+
+  FML_DISALLOW_COPY_AND_ASSIGN(FontGlyphAtlas);
+};
+
 }  // namespace impeller
diff --git a/impeller/typographer/lazy_glyph_atlas.cc b/impeller/typographer/lazy_glyph_atlas.cc
index bf45acd..fd5cf27 100644
--- a/impeller/typographer/lazy_glyph_atlas.cc
+++ b/impeller/typographer/lazy_glyph_atlas.cc
@@ -26,15 +26,15 @@
 void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame, Scalar scale) {
   FML_DCHECK(atlas_map_.empty());
   if (frame.GetAtlasType() == GlyphAtlas::Type::kAlphaBitmap) {
-    frame.CollectUniqueFontGlyphPairs(alpha_set_, scale);
+    frame.CollectUniqueFontGlyphPairs(alpha_glyph_map_, scale);
   } else {
-    frame.CollectUniqueFontGlyphPairs(color_set_, scale);
+    frame.CollectUniqueFontGlyphPairs(color_glyph_map_, scale);
   }
 }
 
 void LazyGlyphAtlas::ResetTextFrames() {
-  alpha_set_.clear();
-  color_set_.clear();
+  alpha_glyph_map_.clear();
+  color_glyph_map_.clear();
   atlas_map_.clear();
 }
 
@@ -59,11 +59,12 @@
     return nullptr;
   }
 
-  auto& set = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_set_ : color_set_;
+  auto& glyph_map = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_glyph_map_
+                                                           : color_glyph_map_;
   auto atlas_context =
       type == GlyphAtlas::Type::kAlphaBitmap ? alpha_context_ : color_context_;
-  auto atlas =
-      typographer_context_->CreateGlyphAtlas(context, type, atlas_context, set);
+  auto atlas = typographer_context_->CreateGlyphAtlas(context, type,
+                                                      atlas_context, glyph_map);
   if (!atlas || !atlas->IsValid()) {
     VALIDATION_LOG << "Could not create valid atlas.";
     return nullptr;
diff --git a/impeller/typographer/lazy_glyph_atlas.h b/impeller/typographer/lazy_glyph_atlas.h
index 3794a7d..b6e8d04 100644
--- a/impeller/typographer/lazy_glyph_atlas.h
+++ b/impeller/typographer/lazy_glyph_atlas.h
@@ -32,8 +32,8 @@
  private:
   std::shared_ptr<TypographerContext> typographer_context_;
 
-  FontGlyphPair::Set alpha_set_;
-  FontGlyphPair::Set color_set_;
+  FontGlyphMap alpha_glyph_map_;
+  FontGlyphMap color_glyph_map_;
   std::shared_ptr<GlyphAtlasContext> alpha_context_;
   std::shared_ptr<GlyphAtlasContext> color_context_;
   mutable std::unordered_map<GlyphAtlas::Type, std::shared_ptr<GlyphAtlas>>
diff --git a/impeller/typographer/text_frame.cc b/impeller/typographer/text_frame.cc
index 9c02a73..3f82c1d 100644
--- a/impeller/typographer/text_frame.cc
+++ b/impeller/typographer/text_frame.cc
@@ -67,12 +67,13 @@
   return std::round(scale * 100) / 100;
 }
 
-void TextFrame::CollectUniqueFontGlyphPairs(FontGlyphPair::Set& set,
+void TextFrame::CollectUniqueFontGlyphPairs(FontGlyphMap& glyph_map,
                                             Scalar scale) const {
   for (const TextRun& run : GetRuns()) {
     const Font& font = run.GetFont();
     auto rounded_scale =
         RoundScaledFontSize(scale, font.GetMetrics().point_size);
+    auto& set = glyph_map[{font, rounded_scale}];
     for (const TextRun::GlyphPosition& glyph_position :
          run.GetGlyphPositions()) {
 #if false
@@ -82,7 +83,7 @@
   FML_LOG(ERROR) << glyph_position.glyph.bounds.size * delta;
 }
 #endif
-      set.insert({font, glyph_position.glyph, rounded_scale});
+      set.insert(glyph_position.glyph);
     }
   }
 }
diff --git a/impeller/typographer/text_frame.h b/impeller/typographer/text_frame.h
index 3531635..d0d2d03 100644
--- a/impeller/typographer/text_frame.h
+++ b/impeller/typographer/text_frame.h
@@ -24,7 +24,7 @@
 
   ~TextFrame();
 
-  void CollectUniqueFontGlyphPairs(FontGlyphPair::Set& set, Scalar scale) const;
+  void CollectUniqueFontGlyphPairs(FontGlyphMap& glyph_map, Scalar scale) const;
 
   static Scalar RoundScaledFontSize(Scalar scale, Scalar point_size);
 
diff --git a/impeller/typographer/typographer_context.h b/impeller/typographer/typographer_context.h
index 8ed74ce..1ca4d4a 100644
--- a/impeller/typographer/typographer_context.h
+++ b/impeller/typographer/typographer_context.h
@@ -36,7 +36,7 @@
       Context& context,
       GlyphAtlas::Type type,
       std::shared_ptr<GlyphAtlasContext> atlas_context,
-      const FontGlyphPair::Set& font_glyph_pairs) const = 0;
+      const FontGlyphMap& font_glyph_map) const = 0;
 
  protected:
   //----------------------------------------------------------------------------
diff --git a/impeller/typographer/typographer_unittests.cc b/impeller/typographer/typographer_unittests.cc
index fd41229..a996bdf 100644
--- a/impeller/typographer/typographer_unittests.cc
+++ b/impeller/typographer/typographer_unittests.cc
@@ -29,10 +29,10 @@
     Scalar scale,
     const std::shared_ptr<GlyphAtlasContext>& atlas_context,
     const TextFrame& frame) {
-  FontGlyphPair::Set set;
-  frame.CollectUniqueFontGlyphPairs(set, scale);
+  FontGlyphMap font_glyph_map;
+  frame.CollectUniqueFontGlyphPairs(font_glyph_map, scale);
   return typographer_context->CreateGlyphAtlas(context, type, atlas_context,
-                                               set);
+                                               font_glyph_map);
 }
 
 TEST_P(TypographerTest, CanConvertTextBlob) {
@@ -68,17 +68,22 @@
   ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap);
   ASSERT_EQ(atlas->GetGlyphCount(), 4llu);
 
-  std::optional<FontGlyphPair> first_pair;
+  std::optional<impeller::ScaledFont> first_scaled_font;
+  std::optional<impeller::Glyph> first_glyph;
   Rect first_rect;
-  atlas->IterateGlyphs(
-      [&](const FontGlyphPair& pair, const Rect& rect) -> bool {
-        first_pair = pair;
-        first_rect = rect;
-        return false;
-      });
+  atlas->IterateGlyphs([&](const ScaledFont& scaled_font, const Glyph& glyph,
+                           const Rect& rect) -> bool {
+    first_scaled_font = scaled_font;
+    first_glyph = glyph;
+    first_rect = rect;
+    return false;
+  });
 
-  ASSERT_TRUE(first_pair.has_value());
-  ASSERT_TRUE(atlas->FindFontGlyphBounds(first_pair.value()).has_value());
+  ASSERT_TRUE(first_scaled_font.has_value());
+  ASSERT_TRUE(atlas
+                  ->FindFontGlyphBounds(
+                      {first_scaled_font.value(), first_glyph.value()})
+                  .has_value());
 }
 
 static sk_sp<SkData> OpenFixtureAsSkData(const char* fixture_name) {
@@ -189,25 +194,26 @@
   auto blob = SkTextBlob::MakeFromString(test_string, sk_font);
   ASSERT_TRUE(blob);
 
-  FontGlyphPair::Set set;
+  FontGlyphMap font_glyph_map;
   size_t size_count = 8;
   for (size_t index = 0; index < size_count; index += 1) {
     MakeTextFrameFromTextBlobSkia(blob).value().CollectUniqueFontGlyphPairs(
-        set, 0.6 * index);
+        font_glyph_map, 0.6 * index);
   };
   auto atlas =
       context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap,
-                                std::move(atlas_context), set);
+                                std::move(atlas_context), font_glyph_map);
   ASSERT_NE(atlas, nullptr);
   ASSERT_NE(atlas->GetTexture(), nullptr);
 
   std::set<uint16_t> unique_glyphs;
   std::vector<uint16_t> total_glyphs;
-  atlas->IterateGlyphs([&](const FontGlyphPair& pair, const Rect& rect) {
-    unique_glyphs.insert(pair.glyph.index);
-    total_glyphs.push_back(pair.glyph.index);
-    return true;
-  });
+  atlas->IterateGlyphs(
+      [&](const ScaledFont& scaled_font, const Glyph& glyph, const Rect& rect) {
+        unique_glyphs.insert(glyph.index);
+        total_glyphs.push_back(glyph.index);
+        return true;
+      });
 
   EXPECT_EQ(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
   EXPECT_EQ(total_glyphs.size(), atlas->GetGlyphCount());
@@ -284,24 +290,6 @@
   ASSERT_NE(old_packer, new_packer);
 }
 
-TEST_P(TypographerTest, FontGlyphPairTypeChangesHashAndEquals) {
-  Font font = Font(nullptr, {});
-  FontGlyphPair pair_1 = {
-      .font = font,
-      .glyph = Glyph(0, Glyph::Type::kBitmap, Rect::MakeXYWH(0, 0, 1, 1))};
-  // Same glyph same type.
-  FontGlyphPair pair_2 = {
-      .font = font,
-      .glyph = Glyph(0, Glyph::Type::kBitmap, Rect::MakeXYWH(0, 0, 1, 1))};
-  // Same glyph different type.
-  FontGlyphPair pair_3 = {
-      .font = font,
-      .glyph = Glyph(0, Glyph::Type::kPath, Rect::MakeXYWH(0, 0, 1, 1))};
-
-  ASSERT_TRUE(FontGlyphPair::Equal{}(pair_1, pair_2));
-  ASSERT_FALSE(FontGlyphPair::Equal{}(pair_1, pair_3));
-}
-
 TEST_P(TypographerTest, MaybeHasOverlapping) {
   sk_sp<SkFontMgr> font_mgr = SkFontMgr::RefDefault();
   sk_sp<SkTypeface> typeface =