New more efficient run builder

A system for building glyph runs. In the future the builder will
only live in canvas, but it's internal structures facilitate
interacting with the cache a single glyph at a time. When all
the bulk code is in place, only runs will be passed around.

Passing the builder down the text draw stack is temporary.

Change-Id: I6e3ed184b3f3a58b919377f2d31936e971bd8efa
Reviewed-on: https://skia-review.googlesource.com/132928
Reviewed-by: Herb Derby <herb@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Herb Derby <herb@google.com>
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 4a6251f..8494bdb 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -591,7 +591,7 @@
         device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect);
     }
 
-    fScratchGlyphSet = skstd::make_unique<SkGlyphSet>();
+    fScratchGlyphRunBuilder = skstd::make_unique<SkGlyphRunBuilder>();
 }
 
 SkCanvas::SkCanvas()
@@ -2449,9 +2449,8 @@
     LOOPER_BEGIN(paint, SkDrawFilter::kText_Type, nullptr)
 
     while (iter.next()) {
-        auto glyphRun = SkGlyphRun::MakeFromDrawText(
-                looper.paint(), text, byteLength, SkPoint::Make(x, y), fScratchGlyphSet.get());
-        iter.fDevice->drawGlyphRun(looper.paint(), &glyphRun);
+        fScratchGlyphRunBuilder->prepareDrawText(paint, text, byteLength, SkPoint::Make(x, y));
+        iter.fDevice->drawGlyphRun(looper.paint(), fScratchGlyphRunBuilder.get());
     }
 
     LOOPER_END
@@ -2463,9 +2462,8 @@
     LOOPER_BEGIN(paint, SkDrawFilter::kText_Type, nullptr)
 
     while (iter.next()) {
-        auto glyphRun = SkGlyphRun::MakeFromDrawPosText(
-                looper.paint(), text, byteLength, pos, fScratchGlyphSet.get());
-        iter.fDevice->drawGlyphRun(looper.paint(), &glyphRun);
+        fScratchGlyphRunBuilder->prepareDrawPosText(paint, text, byteLength, pos);
+        iter.fDevice->drawGlyphRun(looper.paint(), fScratchGlyphRunBuilder.get());
     }
 
     LOOPER_END
@@ -2477,10 +2475,8 @@
     LOOPER_BEGIN(paint, SkDrawFilter::kText_Type, nullptr)
 
     while (iter.next()) {
-        auto glyphRun =
-                SkGlyphRun::MakeFromDrawPosTextH(
-                        looper.paint(), text, byteLength, xpos, constY, fScratchGlyphSet.get());
-        iter.fDevice->drawGlyphRun(looper.paint(), &glyphRun);
+        fScratchGlyphRunBuilder->prepareDrawPosTextH(paint, text, byteLength, xpos, constY);
+        iter.fDevice->drawGlyphRun(looper.paint(), fScratchGlyphRunBuilder.get());
     }
 
     LOOPER_END
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 8321718..aa9b1e2 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -160,14 +160,10 @@
 
         switch (it.positioning()) {
         case SkTextBlob::kDefault_Positioning: {
-            SkGlyphSet glyphSet;
             auto origin = SkPoint::Make(x + offset.x(), y + offset.y());
-            auto glyphRun =
-                    SkGlyphRun::MakeFromDrawText(
-                            runPaint, (const char*) it.glyphs(), textLen, origin, &glyphSet);
-            this->drawPosText(
-                    it.glyphs(), textLen, glyphRun.getPositions(), 2,
-                    SkPoint::Make(0, 0), runPaint);
+            SkGlyphRunBuilder builder;
+            builder.prepareDrawText(runPaint, (const char*) it.glyphs(), textLen, origin);
+            builder.temporaryShuntToDrawPosText(runPaint, this);
         }
         break;
         case SkTextBlob::kHorizontal_Positioning:
@@ -253,15 +249,11 @@
     }
 }
 
-void SkBaseDevice::drawGlyphRun(const SkPaint& paint, SkGlyphRun* info) {
+void SkBaseDevice::drawGlyphRun(const SkPaint& paint, SkGlyphRunBuilder* runBuilder) {
     SkPaint glyphPaint(paint);
     glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
-    auto glyphs = info->copyGlyphIDs();
-
-    this->drawPosText(
-            glyphs.get(), info->runSize() * 2,
-            info->getPositions(), 2, SkPoint::Make(0, 0), glyphPaint);
+    runBuilder->temporaryShuntToDrawPosText(glyphPaint, this);
 }
 
 void SkBaseDevice::drawBitmapLattice(const SkBitmap& bitmap,
diff --git a/src/core/SkDevice.h b/src/core/SkDevice.h
index d36dc4d..2500833 100644
--- a/src/core/SkDevice.h
+++ b/src/core/SkDevice.h
@@ -18,6 +18,7 @@
 class SkDrawFilter;
 struct SkDrawShadowRec;
 class SkGlyphRun;
+class SkGlyphRunBuilder;
 class SkImageFilterCache;
 struct SkIRect;
 class SkMatrix;
@@ -223,7 +224,7 @@
      *  Does not handle text decoration.
      *  Decorations (underline and stike-thru) will be handled by SkCanvas.
      */
-    virtual void drawGlyphRun(const SkPaint& paint, SkGlyphRun* info);
+    virtual void drawGlyphRun(const SkPaint& paint, SkGlyphRunBuilder* info);
     virtual void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) = 0;
     virtual void drawShadow(const SkPath&, const SkDrawShadowRec&);
 
@@ -346,6 +347,8 @@
     friend class SkSurface_Raster;
     friend class DeviceTestingAccess;
 
+    // Temporarily friend the SkGlyphRunBuilder until drawPosText is gone.
+    friend class SkGlyphRunBuilder;
     virtual void drawPosText(const void* text, size_t len,
                              const SkScalar pos[], int scalarsPerPos,
                              const SkPoint& offset, const SkPaint& paint) = 0;
diff --git a/src/core/SkGlyphRun.cpp b/src/core/SkGlyphRun.cpp
index 41f5013..ce4c907 100644
--- a/src/core/SkGlyphRun.cpp
+++ b/src/core/SkGlyphRun.cpp
@@ -10,6 +10,7 @@
 #include <algorithm>
 #include <tuple>
 
+#include "SkDevice.h"
 #include "SkDraw.h"
 #include "SkGlyphCache.h"
 #include "SkMakeUnique.h"
@@ -19,8 +20,6 @@
 #include "SkStrikeCache.h"
 #include "SkUtils.h"
 
-namespace {
-
 static SkTypeface::Encoding convert_encoding(SkPaint::TextEncoding encoding) {
     switch (encoding) {
         case  SkPaint::kUTF8_TextEncoding: return SkTypeface::kUTF8_Encoding;
@@ -30,150 +29,7 @@
     }
 }
 
-using Core = std::tuple<size_t,   std::unique_ptr<uint16_t[]>, std::vector<SkGlyphID>>;
-
-Core make_from_glyphids(
-        size_t glyphCount, const SkGlyphID* glyphs, SkGlyphID maxGlyphID, SkGlyphSet* glyphSet) {
-    if (glyphCount == 0) { return Core(0, nullptr, std::vector<SkGlyphID>()); }
-
-    glyphSet->reuse(maxGlyphID);
-
-    auto denseIndex = skstd::make_unique_default<uint16_t[]>(glyphCount);
-    for (size_t i = 0; i < glyphCount; i++) {
-        denseIndex[i] = glyphSet->add(glyphs[i]);
-    }
-
-    return Core(glyphCount, std::move(denseIndex), glyphSet->uniqueGlyphIDs());
-}
-
-Core make_from_utfn(size_t byteLength, const void* utfN, const SkTypeface& typeface,
-                    SkTypeface::Encoding encoding, SkGlyphSet* glyphSet) {
-    auto count = SkUTFN_CountUnichars(encoding, utfN, byteLength);
-
-    if (count <= 0) {
-        return Core(0, nullptr, std::vector<SkGlyphID>());
-    }
-
-    auto glyphs = skstd::make_unique_default<SkGlyphID[]>(count);
-
-    // TODO: move to using cached version.
-    typeface.charsToGlyphs(utfN, encoding, glyphs.get(), count);
-
-    return make_from_glyphids(count, glyphs.get(), typeface.countGlyphs(), glyphSet);
-}
-
-Core make_core(const SkPaint& paint, const void* bytes, size_t byteLength, SkGlyphSet* glyphSet) {
-    auto encoding = paint.getTextEncoding();
-    auto typeface = SkPaintPriv::GetTypefaceOrDefault(paint);
-    if (encoding == SkPaint::kGlyphID_TextEncoding) {
-        return make_from_glyphids(
-                byteLength / 2, reinterpret_cast<const SkGlyphID*>(bytes),
-                typeface->countGlyphs(), glyphSet);
-    } else {
-        return make_from_utfn(byteLength, bytes, *typeface, convert_encoding(encoding), glyphSet);
-    }
-}
-
-}  // namespace
-
-SkGlyphRun SkGlyphRun::MakeFromDrawText(
-        const SkPaint& paint, const void* bytes, size_t byteLength,
-        const SkPoint origin, SkGlyphSet* glyphSet) {
-    size_t runSize;
-    std::unique_ptr<uint16_t[]> denseIndex;
-    std::vector<SkGlyphID> uniqueGlyphIDs;
-    std::tie(runSize, denseIndex, uniqueGlyphIDs) = make_core(paint, bytes, byteLength, glyphSet);
-
-    if (runSize == 0) { return SkGlyphRun{}; }
-
-    auto advances = skstd::make_unique_default<SkPoint[]>(uniqueGlyphIDs.size());
-
-    {
-        auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(paint);
-        cache->getAdvances(SkSpan<SkGlyphID>{uniqueGlyphIDs.data(),
-                                             uniqueGlyphIDs.size()}, advances.get());
-    }
-
-    auto positions = skstd::make_unique_default<SkPoint[]>(runSize);
-
-    SkPoint endOfLastGlyph = origin;
-
-    for (size_t i = 0; i < runSize; i++) {
-        positions[i] = endOfLastGlyph;
-        endOfLastGlyph += advances[denseIndex[i]];
-    }
-
-    if (paint.getTextAlign() != SkPaint::kLeft_Align) {
-        SkVector len = endOfLastGlyph - origin;
-        if (paint.getTextAlign() == SkPaint::kCenter_Align) {
-            len.scale(SK_ScalarHalf);
-        }
-        for (size_t i = 0; i < runSize; i++) {
-            positions[i] -= len;
-        }
-    }
-
-    return SkGlyphRun{
-        runSize, std::move(denseIndex), std::move(positions), std::move(uniqueGlyphIDs)};
-}
-
-SkGlyphRun SkGlyphRun::MakeFromDrawPosTextH(
-        const SkPaint& paint, const void* bytes, size_t byteLength,
-        const SkScalar xpos[], SkScalar constY, SkGlyphSet* glyphSet) {
-    size_t runSize;
-    std::unique_ptr<uint16_t[]> denseIndex;
-    std::vector<SkGlyphID> uniqueGlyphIDs;
-    std::tie(runSize, denseIndex, uniqueGlyphIDs) = make_core(paint, bytes, byteLength, glyphSet);
-
-    if (runSize == 0) { return SkGlyphRun{}; }
-
-    auto positions = skstd::make_unique_default<SkPoint[]>(runSize);
-
-    for (size_t i = 0; i < runSize; i++) {
-        positions[i] = SkPoint::Make(xpos[i], constY);
-    }
-
-    return SkGlyphRun{
-        runSize, std::move(denseIndex), std::move(positions), std::move(uniqueGlyphIDs)};
-}
-
-SkGlyphRun SkGlyphRun::MakeFromDrawPosText(
-        const SkPaint& paint, const void* bytes, size_t byteLength,
-        const SkPoint pos[], SkGlyphSet* glyphSet) {
-    size_t runSize;
-    std::unique_ptr<uint16_t[]> denseIndex;
-    std::vector<SkGlyphID> uniqueGlyphIDs;
-    std::tie(runSize, denseIndex, uniqueGlyphIDs) = make_core(paint, bytes, byteLength, glyphSet);
-
-    if (runSize == 0) { return SkGlyphRun{}; }
-
-    auto positions = skstd::make_unique_default<SkPoint[]>(runSize);
-
-    memcpy(positions.get(), pos, sizeof(SkPoint) * runSize);
-
-    return SkGlyphRun{
-        runSize, std::move(denseIndex), std::move(positions), std::move(uniqueGlyphIDs)};
-}
-
-std::unique_ptr<SkGlyphID[]> SkGlyphRun::copyGlyphIDs() const {
-    auto glyphs = skstd::make_unique_default<SkGlyphID[]>(fRunSize);
-
-    for (size_t i = 0; i < fRunSize; i++) {
-        glyphs[i] = fUniqueGlyphs[fDenseIndex[i]];
-    }
-
-    return glyphs;
-}
-
-SkGlyphRun::SkGlyphRun(size_t runSize,
-                       std::unique_ptr<uint16_t[]>&& denseIndex,
-                       std::unique_ptr<SkPoint[]>&& positions,
-                       std::vector<SkGlyphID>&& uniqueGlyphIDs)
-    : fDenseIndex{std::move(denseIndex)}
-    , fPositions{std::move(positions)}
-    , fUniqueGlyphs{std::move(uniqueGlyphIDs)}
-    , fRunSize{runSize} { }
-
+// -- SkGlyphSet ----------------------------------------------------------------------------------
 uint16_t SkGlyphSet::add(SkGlyphID glyphID) {
     static constexpr SkGlyphID  kUndefGlyph{0};
 
@@ -186,31 +42,28 @@
     }
 
     auto index = fIndices[glyphID];
-    if (index < fUniqueGlyphIDs.size() && fUniqueGlyphIDs[index] == glyphID) {
+    if (index < fUniqueGlyphIDs->size() && (*fUniqueGlyphIDs)[index] == glyphID) {
         return index;
     }
 
-    uint16_t newIndex = SkTo<uint16_t>(fUniqueGlyphIDs.size());
-    fUniqueGlyphIDs.push_back(glyphID);
+    uint16_t newIndex = SkTo<uint16_t>(fUniqueGlyphIDs->size());
+    fUniqueGlyphIDs->push_back(glyphID);
     fIndices[glyphID] = newIndex;
     return newIndex;
 }
 
-std::vector<SkGlyphID> SkGlyphSet::uniqueGlyphIDs() {
-    return fUniqueGlyphIDs;
-}
-
-void SkGlyphSet::reuse(uint32_t glyphUniverseSize) {
+void SkGlyphSet::reuse(uint32_t glyphUniverseSize, std::vector<SkGlyphID>* uniqueGlyphIDs) {
     SkASSERT(glyphUniverseSize <= (1 << 16));
     fUniverseSize = glyphUniverseSize;
+    fUniqueGlyphIDs = uniqueGlyphIDs;
     // If we're hanging onto these arrays for a long time, we don't want their size to drift
     // endlessly upwards. It's unusual to see more than 256 unique glyphs used in a run,
     // or a typeface with more than 4096 possible glyphs.
-    if (fUniqueGlyphIDs.size() > 256) {
-        fUniqueGlyphIDs.resize(256);
-        fUniqueGlyphIDs.shrink_to_fit();
+    if (fUniqueGlyphIDs->size() > 256) {
+        fUniqueGlyphIDs->resize(256);
+        fUniqueGlyphIDs->shrink_to_fit();
     }
-    fUniqueGlyphIDs.clear();
+    fUniqueGlyphIDs->clear();
 
     if (glyphUniverseSize < 4096 && fIndices.size() > 4096) {
         fIndices.resize(4096);
@@ -219,4 +72,114 @@
 
     // No need to clear fIndices here... SkGlyphSet's set insertion algorithm is designed to work
     // correctly even when the fIndexes buffer is uninitialized!
-}
\ No newline at end of file
+}
+
+// -- SkGlyphRunBuilder ----------------------------------------------------------------------------
+void SkGlyphRunBuilder::prepareDrawText(
+        const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin) {
+
+    this->initializeDenseAndUnique(paint, bytes, byteLength);
+
+    fScratchAdvances.resize(this->uniqueSize());
+    {
+        auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(paint);
+        cache->getAdvances(SkSpan<SkGlyphID>{fUniqueGlyphs}, fScratchAdvances.data());
+    }
+
+    SkPoint endOfLastGlyph = origin;
+
+    for (size_t i = 0; i < this->runSize(); i++) {
+        fPositions.push_back(endOfLastGlyph);
+        endOfLastGlyph += fScratchAdvances[fDenseIndex[i]];
+    }
+
+    if (paint.getTextAlign() != SkPaint::kLeft_Align) {
+        SkVector len = endOfLastGlyph - origin;
+        if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+            len.scale(SK_ScalarHalf);
+        }
+        for (size_t i = 0; i < this->runSize(); i++) {
+            fPositions[i] -= len;
+        }
+    }
+
+}
+
+void SkGlyphRunBuilder::prepareDrawPosTextH(const SkPaint& paint, const void* bytes,
+                                            size_t byteLength, const SkScalar* xpos,
+                                            SkScalar constY) {
+
+    this->initializeDenseAndUnique(paint, bytes, byteLength);
+
+    for (size_t i = 0; i < runSize(); i++) {
+        fPositions.push_back(SkPoint::Make(xpos[i], constY));
+    }
+}
+
+void SkGlyphRunBuilder::prepareDrawPosText(const SkPaint& paint, const void* bytes,
+                                           size_t byteLength, const SkPoint* pos) {
+    this->initializeDenseAndUnique(paint, bytes, byteLength);
+
+    for (size_t i = 0; i < runSize(); i++) {
+        fPositions.push_back(pos[i]);
+    }
+}
+
+const SkGlyphRun& SkGlyphRunBuilder::useGlyphRun() const {
+    new ((void*)&fScratchGlyphRun) SkGlyphRun{SkSpan<uint16_t>(fDenseIndex),
+                                       SkSpan<SkPoint>(fPositions),
+                                       SkSpan<SkGlyphID>(fUniqueGlyphs)};
+    return fScratchGlyphRun;
+}
+
+void SkGlyphRunBuilder::temporaryShuntToDrawPosText(const SkPaint& paint, SkBaseDevice* device) {
+
+    auto pos = (const SkScalar*) fPositions.data();
+
+    device->drawPosText(
+            fTemporaryShuntGlyphIDs, fDenseIndex.size() * 2,
+            pos, 2, SkPoint::Make(0, 0), paint);
+}
+
+void SkGlyphRunBuilder::temporaryShuntToCallback(TemporaryShuntCallback callback) {
+    auto bytes = (const char *)fTemporaryShuntGlyphIDs;
+    auto pos = (const SkScalar*)fPositions.data();
+    callback(this->runSize(), bytes, pos);
+}
+
+void SkGlyphRunBuilder::initializeDenseAndUnique(
+        const SkPaint& paint, const void* bytes, size_t byteLength) {
+
+    fDenseIndex.clear();
+    fPositions.clear();
+    fUniqueGlyphs.clear();
+    fTemporaryShuntGlyphIDs = nullptr;
+
+    size_t runSize = 0;
+    const SkGlyphID* glyphIDs = nullptr;
+    auto encoding = paint.getTextEncoding();
+    auto typeface = SkPaintPriv::GetTypefaceOrDefault(paint);
+    if (encoding != SkPaint::kGlyphID_TextEncoding) {
+        auto tfEncoding = convert_encoding(encoding);
+        int utfSize = SkUTFN_CountUnichars(tfEncoding, bytes, byteLength);
+        if (utfSize > 0) {
+            runSize = SkTo<size_t>(utfSize);
+            fScratchGlyphIDs.resize(runSize);
+            typeface->charsToGlyphs(bytes, tfEncoding, fScratchGlyphIDs.data(), runSize);
+            glyphIDs = fScratchGlyphIDs.data();
+        }
+    } else {
+        runSize = byteLength / 2;
+        glyphIDs = (const SkGlyphID*)bytes;
+    }
+
+    if (runSize == 0) { return; }
+    fTemporaryShuntGlyphIDs = glyphIDs;
+
+    fGlyphSet.reuse(typeface->countGlyphs(), &fUniqueGlyphs);
+    for (size_t i = 0; i < runSize; i++) {
+        fDenseIndex.push_back(fGlyphSet.add(glyphIDs[i]));
+    }
+}
+
+
diff --git a/src/core/SkGlyphRun.h b/src/core/SkGlyphRun.h
index 53d57af..eeb9c29 100644
--- a/src/core/SkGlyphRun.h
+++ b/src/core/SkGlyphRun.h
@@ -8,6 +8,7 @@
 #ifndef SkGlyphRunInfo_DEFINED
 #define SkGlyphRunInfo_DEFINED
 
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -17,63 +18,14 @@
 #include "SkPoint.h"
 #include "SkTypes.h"
 
-// A faster set implementation that does not need any initialization, and reading the set items
-// is order the number of items, and not the size of the universe.
-// This implementation is based on the paper by Briggs and Torczon, "An Efficient Representation
-// for Sparse Sets"
-class SkGlyphSet {
-public:
-    SkGlyphSet() = default;
-    uint16_t add(SkGlyphID glyphID);
-    std::vector<SkGlyphID> uniqueGlyphIDs();
-    void reuse(uint32_t glyphUniverseSize);
-
-private:
-    uint32_t                    fUniverseSize{0};
-    std::vector<uint16_t>       fIndices;
-    std::vector<SkGlyphID>      fUniqueGlyphIDs;
-};
-
-class SkGlyphRun {
-public:
-    SkGlyphRun() = default;
-    SkGlyphRun(SkGlyphRun&&) = default;
-    static SkGlyphRun MakeFromDrawText(
-            const SkPaint& paint, const void* bytes, size_t byteLength,
-            SkPoint origin, SkGlyphSet* glyphSet);
-    static SkGlyphRun MakeFromDrawPosTextH(
-            const SkPaint& paint, const void* bytes, size_t byteLength,
-            const SkScalar xpos[], SkScalar constY, SkGlyphSet* glyphSet);
-    static SkGlyphRun MakeFromDrawPosText(
-            const SkPaint& paint, const void* bytes, size_t byteLength,
-            const SkPoint pos[], SkGlyphSet* glyphSet);
-
-    size_t runSize() const { return fRunSize; }
-    uint16_t uniqueSize() const { return fUniqueGlyphs.size(); }
-
-    // copyGlyphIDs is temporary glue to work with the existing system. Don't use with new code.
-    std::unique_ptr<SkGlyphID[]> copyGlyphIDs() const;
-    const SkScalar* getPositions() const {
-        return reinterpret_cast<const SkScalar*>(fPositions.get());
-    }
-
-private:
-    SkGlyphRun(size_t runSize,
-               std::unique_ptr<uint16_t[]>&& denseIndex,
-               std::unique_ptr<SkPoint[]>&& positions,
-               std::vector<SkGlyphID>&& uniqueGlyphIDs);
-
-    std::unique_ptr<uint16_t[]>  fDenseIndex;
-    std::unique_ptr<SkPoint[]>   fPositions;
-    std::vector<SkGlyphID>       fUniqueGlyphs;
-    const size_t                 fRunSize{0};
-};
+class SkBaseDevice;
 
 template <typename T>
 class SkSpan {
 public:
+    SkSpan() = default;
     SkSpan(const T* ptr, size_t size) : fPtr{ptr}, fSize{size} {}
-    SkSpan(const std::vector<T>& v) : fPtr{v.data()}, fSize{v.size()} {}
+    explicit SkSpan(const std::vector<T>& v) : fPtr{v.data()}, fSize{v.size()} {}
     const T& operator [] (ptrdiff_t i) const { return fPtr[i]; }
     const T* begin() const { return fPtr; }
     const T* end() const { return fPtr + fSize; }
@@ -84,4 +36,90 @@
     size_t fSize;
 };
 
+class SkGlyphRun {
+public:
+    SkGlyphRun() = default;
+    SkGlyphRun(SkSpan<uint16_t> denseIndex, SkSpan<SkPoint> positions,
+               SkSpan<SkGlyphID> uniqueGlyphIDs)
+            : fDenseIndex{denseIndex}
+            , fPositions{positions}
+            , fUniqueGlyphIDs{uniqueGlyphIDs} {}
+
+    size_t runSize() const { return fDenseIndex.size(); }
+    uint16_t uniqueSize() const { return fUniqueGlyphIDs.size(); }
+    SkSpan<SkPoint> positions() const { return SkSpan<SkPoint>(fPositions); }
+
+private:
+    SkSpan<uint16_t>  fDenseIndex;
+    SkSpan<SkPoint>   fPositions;
+    SkSpan<SkGlyphID> fUniqueGlyphIDs;
+};
+
+// A faster set implementation that does not need any initialization, and reading the set items
+// is order the number of items, and not the size of the universe.
+// This implementation is based on the paper by Briggs and Torczon, "An Efficient Representation
+// for Sparse Sets"
+class SkGlyphSet {
+public:
+    SkGlyphSet() = default;
+    uint16_t add(SkGlyphID glyphID);
+    void reuse(uint32_t glyphUniverseSize, std::vector<SkGlyphID>* uniqueGlyphIDs);
+
+private:
+    uint32_t                    fUniverseSize{0};
+    std::vector<uint16_t>       fIndices;
+    std::vector<SkGlyphID>*     fUniqueGlyphIDs{nullptr};
+};
+
+// Currently the old code is passing around SkGlyphRunBuilder because it facilitates working in the
+// old single glyph lookup style with the cache. When the lower level code is transitioned over to
+// the bulk glyph cache style, then the builder will only be used in the canvas, and only runs will
+// be passed around.
+class SkGlyphRunBuilder {
+public:
+    SkGlyphRunBuilder() = default;
+    void prepareDrawText(
+            const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin);
+    void prepareDrawPosTextH(
+            const SkPaint& paint, const void* bytes, size_t byteLength,
+            const SkScalar xpos[], SkScalar constY);
+    void prepareDrawPosText(
+            const SkPaint& paint, const void* bytes, size_t byteLength, const SkPoint pos[]);
+
+    size_t runSize() const {return fDenseIndex.size();}
+    size_t uniqueSize() const {return fUniqueGlyphs.size();}
+
+    const SkGlyphRun& useGlyphRun() const;
+
+    // The temporaryShunt calls are to allow inter-operating with existing code while glyph runs
+    // are developed.
+    void temporaryShuntToDrawPosText(const SkPaint& paint, SkBaseDevice* device);
+    using TemporaryShuntCallback = std::function<void(size_t, const char*, const SkScalar*)>;
+    void temporaryShuntToCallback(TemporaryShuntCallback callback);
+
+private:
+    void initializeDenseAndUnique(const SkPaint& paint, const void* bytes, size_t byteLength);
+
+    std::vector<uint16_t>  fDenseIndex;
+    std::vector<SkPoint>   fPositions;
+    std::vector<SkGlyphID> fUniqueGlyphs;
+
+    // Used as a temporary for preparing using utfN text.
+    std::vector<SkGlyphID> fScratchGlyphIDs;
+
+    // Used as temporary storage for calculating positions for drawText.
+    std::vector<SkPoint>   fScratchAdvances;
+
+    // Used to temporarily use of a glyph run for bulk cache API calls (just an experiment at
+    // this point).
+    SkGlyphRun             fScratchGlyphRun;
+
+    // Used as an aid to shunt from glyph runs to drawPosText. It will either be fScratchIDs or
+    // the bytes passed in.
+    const SkGlyphID*       fTemporaryShuntGlyphIDs{nullptr};
+
+    // Used for collecting the set of unique glyphs.
+    SkGlyphSet            fGlyphSet;
+};
+
 #endif  // SkGlyphRunInfo_DEFINED
diff --git a/src/gpu/text/GrTextContext.cpp b/src/gpu/text/GrTextContext.cpp
index 9864d02..b8fe456 100644
--- a/src/gpu/text/GrTextContext.cpp
+++ b/src/gpu/text/GrTextContext.cpp
@@ -210,15 +210,18 @@
                                     shaderCaps.supportsDistanceFieldText(), fOptions)) {
             switch (it.positioning()) {
                 case SkTextBlob::kDefault_Positioning: {
-                    SkGlyphSet glyphSet;
                     auto origin = SkPoint::Make(x + offset.x(), y + offset.y());
-                    auto glyphRun =
-                            SkGlyphRun::MakeFromDrawText(runPaint.skPaint(),
-                                    (const char*)it.glyphs(), textLen, origin, &glyphSet);
+                    SkGlyphRunBuilder builder;
+                    builder.prepareDrawText(runPaint.skPaint(),
+                                            (const char*)it.glyphs(), textLen, origin);
 
-                    this->drawDFPosText(cacheBlob, run, glyphCache, props, runPaint,
-                                        scalerContextFlags, viewMatrix, (const char*)it.glyphs(),
-                                        textLen, glyphRun.getPositions(), 2, SkPoint::Make(0,0));
+                    builder.temporaryShuntToCallback(
+                            [&](size_t runSize, const char* glyphIDs, const SkScalar* pos) {
+                                this->drawDFPosText(
+                                    cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags,
+                                    viewMatrix, glyphIDs, 2 * runSize, pos, 2,
+                                    SkPoint::Make(0,0));
+                            });
                     break;
                 }
 
@@ -240,16 +243,18 @@
         } else {
             switch (it.positioning()) {
                 case SkTextBlob::kDefault_Positioning: {
-                    SkGlyphSet glyphSet;
                     auto origin = SkPoint::Make(x + offset.x(), y + offset.y());
-                    auto glyphRun =
-                            SkGlyphRun::MakeFromDrawText(
-                                    runPaint.skPaint(), (const char*) it.glyphs(), textLen, origin,
-                                    &glyphSet);
+                    SkGlyphRunBuilder builder;
+                    builder.prepareDrawText(runPaint.skPaint(),
+                                            (const char*)it.glyphs(), textLen, origin);
 
-                    this->DrawBmpPosText(cacheBlob, run, glyphCache, props, runPaint,
-                                         scalerContextFlags, viewMatrix, (const char*) it.glyphs(),
-                                         textLen, glyphRun.getPositions(), 2, SkPoint::Make(0, 0));
+                    builder.temporaryShuntToCallback(
+                            [&](size_t runSize, const char* glyphIDs, const SkScalar* pos) {
+                                this->DrawBmpPosText(
+                                    cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags,
+                                    viewMatrix, glyphIDs, 2 * runSize,
+                                    pos, 2, SkPoint::Make(0, 0));
+                            });
                     break;
                 }
                 case SkTextBlob::kHorizontal_Positioning:
@@ -768,15 +773,20 @@
     // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to
     // test the text op with this unit test, that is okay.
 
-    SkGlyphSet glyphSet;
     auto origin = SkPoint::Make(x, y);
-    auto glyphRun = SkGlyphRun::MakeFromDrawText(skPaint, text, textLen, origin, &glyphSet);
+    SkGlyphRunBuilder builder;
+    builder.prepareDrawText(skPaint, text, textLen, origin);
+    sk_sp<GrTextBlob> blob;
 
-    sk_sp<GrTextBlob> blob(textContext->makeDrawPosTextBlob(
-            context->contextPriv().getTextBlobCache(), glyphCache,
-            *context->contextPriv().caps()->shaderCaps(), utilsPaint,
-            GrTextContext::kTextBlobOpScalerContextFlags, viewMatrix, surfaceProps, text,
-            static_cast<size_t>(textLen), glyphRun.getPositions(), 2, origin));
+    // Use the text and textLen below, because we don't want to mess with the paint.
+    builder.temporaryShuntToCallback(
+            [&](size_t runSize, const char* glyphIDs, const SkScalar* pos) {
+                blob = textContext->makeDrawPosTextBlob(
+                    context->contextPriv().getTextBlobCache(), glyphCache,
+                    *context->contextPriv().caps()->shaderCaps(), utilsPaint,
+                    GrTextContext::kTextBlobOpScalerContextFlags, viewMatrix, surfaceProps, text,
+                    textLen, pos, 2, origin);
+            });
 
     return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, utilsPaint, surfaceProps,
                              textContext->dfAdjustTable(), rtc->textTarget());