generate vertex data in onPrepare

Switch to a compact encoding for vertex data
that can be easily expanded in onPrepare.

Bug: skia:10251

Change-Id: I53893c94514a7ff3b4f33be444f4ec2002e63ec4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/290297
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp
index 4e6c197..6372955 100644
--- a/src/atlastext/SkAtlasTextTarget.cpp
+++ b/src/atlastext/SkAtlasTextTarget.cpp
@@ -242,8 +242,6 @@
         auto subRun = fGeoData[i].fSubRunPtr;
         subRun->prepareGrGlyphs(context.grContext()->priv().getGrStrikeCache());
         // TODO4F: Preserve float colors
-        subRun->updateVerticesColorIfNeeded(fGeoData[i].fColor.toBytes_RGBA());
-        subRun->translateVerticesIfNeeded(fGeoData[i].fDrawMatrix, fGeoData[i].fDrawOrigin);
         GrTextBlob::VertexRegenerator regenerator(resourceProvider, subRun, &context, atlasManager);
         int subRunEnd = subRun->glyphCount();
         for (int subRunIndex = 0; subRunIndex < subRunEnd;) {
@@ -252,7 +250,10 @@
                 break;
             }
 
-            context.recordDraw(subRun->quadStart(subRunIndex), glyphsRegenerated,
+            std::unique_ptr<GrTextBlob::Mask3DVertex[][4]> vertexData =
+                    fGeoData[i].textTargetCreateVertexData(subRunIndex, glyphsRegenerated);
+
+            context.recordDraw(vertexData.get(), glyphsRegenerated,
                                fGeoData[i].fDrawMatrix, target->handle());
             subRunIndex += glyphsRegenerated;
             if (subRunIndex != subRunEnd) {
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index cc7611f..8bfda87 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -62,6 +62,22 @@
     this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo);
 }
 
+// Entry point just for the SkAtlasTextTarget
+std::unique_ptr<GrTextBlob::Mask3DVertex[][4]> GrAtlasTextOp::Geometry::textTargetCreateVertexData(
+        int offset, int count) const {
+    std::unique_ptr<GrTextBlob::Mask3DVertex[][4]> data{new GrTextBlob::Mask3DVertex[count][4]};
+
+    fSubRunPtr->fillTextTargetVertexData(data.get(), offset, count, fColor.toBytes_RGBA(),
+                                         fDrawOrigin);
+
+    return data;
+}
+
+void GrAtlasTextOp::Geometry::fillVertexData(void *dst, int offset, int count) const {
+    fSubRunPtr->fillVertexData(dst, offset, count, fColor.toBytes_RGBA(),
+                               fDrawMatrix, fDrawOrigin, fClipRect);
+}
+
 std::unique_ptr<GrAtlasTextOp> GrAtlasTextOp::MakeBitmap(GrRecordingContext* context,
                                                          GrPaint&& paint,
                                                          GrTextBlob::SubRun* subrun,
@@ -193,113 +209,6 @@
     return analysis;
 }
 
-static void clip_quads(const SkIRect& clipRect, char* currVertex, const char* blobVertices,
-                       size_t vertexStride, int glyphCount) {
-    for (int i = 0; i < glyphCount; ++i) {
-        const SkPoint* blobPositionLT = reinterpret_cast<const SkPoint*>(blobVertices);
-        const SkPoint* blobPositionRB =
-                reinterpret_cast<const SkPoint*>(blobVertices + 3 * vertexStride);
-
-        // positions for bitmap glyphs are pixel boundary aligned
-        SkIRect positionRect = SkIRect::MakeLTRB(SkScalarRoundToInt(blobPositionLT->fX),
-                                                 SkScalarRoundToInt(blobPositionLT->fY),
-                                                 SkScalarRoundToInt(blobPositionRB->fX),
-                                                 SkScalarRoundToInt(blobPositionRB->fY));
-        if (clipRect.contains(positionRect)) {
-            memcpy(currVertex, blobVertices, 4 * vertexStride);
-            currVertex += 4 * vertexStride;
-        } else {
-            // Pull out some more data that we'll need.
-            // In the LCD case the color will be garbage, but we'll overwrite it with the texcoords
-            // and it avoids a lot of conditionals.
-            auto color = *reinterpret_cast<const SkColor*>(blobVertices + sizeof(SkPoint));
-            size_t coordOffset = vertexStride - 2*sizeof(uint16_t);
-            auto* blobCoordsLT = reinterpret_cast<const uint16_t*>(blobVertices + coordOffset);
-            auto* blobCoordsRB = reinterpret_cast<const uint16_t*>(blobVertices + 3 * vertexStride +
-                                                                   coordOffset);
-            // Pull out the texel coordinates and texture index bits
-            uint16_t coordsRectL = blobCoordsLT[0];
-            uint16_t coordsRectT = blobCoordsLT[1];
-            uint16_t coordsRectR = blobCoordsRB[0];
-            uint16_t coordsRectB = blobCoordsRB[1];
-            int index0, index1;
-            std::tie(coordsRectL, coordsRectT, index0) =
-                    GrDrawOpAtlas::UnpackIndexFromTexCoords(coordsRectL, coordsRectT);
-            std::tie(coordsRectR, coordsRectB, index1) =
-                    GrDrawOpAtlas::UnpackIndexFromTexCoords(coordsRectR, coordsRectB);
-            SkASSERT(index0 == index1);
-
-            int positionRectWidth = positionRect.width();
-            int positionRectHeight = positionRect.height();
-            SkASSERT(positionRectWidth == (coordsRectR - coordsRectL));
-            SkASSERT(positionRectHeight == (coordsRectB - coordsRectT));
-
-            // Clip position and texCoords to the clipRect
-            unsigned int delta;
-            delta = std::min(std::max(clipRect.fLeft - positionRect.fLeft, 0), positionRectWidth);
-            coordsRectL += delta;
-            positionRect.fLeft += delta;
-
-            delta = std::min(std::max(clipRect.fTop - positionRect.fTop, 0), positionRectHeight);
-            coordsRectT += delta;
-            positionRect.fTop += delta;
-
-            delta = std::min(std::max(positionRect.fRight - clipRect.fRight, 0), positionRectWidth);
-            coordsRectR -= delta;
-            positionRect.fRight -= delta;
-
-            delta = std::min(std::max(positionRect.fBottom - clipRect.fBottom, 0), positionRectHeight);
-            coordsRectB -= delta;
-            positionRect.fBottom -= delta;
-
-            // Repack texel coordinates and index
-            std::tie(coordsRectL, coordsRectT) =
-                    GrDrawOpAtlas::PackIndexInTexCoords(coordsRectL, coordsRectT, index0);
-            std::tie(coordsRectR, coordsRectB) =
-                    GrDrawOpAtlas::PackIndexInTexCoords(coordsRectR, coordsRectB, index1);
-
-            // Set new positions and coords
-            SkPoint* currPosition = reinterpret_cast<SkPoint*>(currVertex);
-            currPosition->fX = positionRect.fLeft;
-            currPosition->fY = positionRect.fTop;
-            *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
-            uint16_t* currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
-            currCoords[0] = coordsRectL;
-            currCoords[1] = coordsRectT;
-            currVertex += vertexStride;
-
-            currPosition = reinterpret_cast<SkPoint*>(currVertex);
-            currPosition->fX = positionRect.fLeft;
-            currPosition->fY = positionRect.fBottom;
-            *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
-            currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
-            currCoords[0] = coordsRectL;
-            currCoords[1] = coordsRectB;
-            currVertex += vertexStride;
-
-            currPosition = reinterpret_cast<SkPoint*>(currVertex);
-            currPosition->fX = positionRect.fRight;
-            currPosition->fY = positionRect.fTop;
-            *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
-            currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
-            currCoords[0] = coordsRectR;
-            currCoords[1] = coordsRectT;
-            currVertex += vertexStride;
-
-            currPosition = reinterpret_cast<SkPoint*>(currVertex);
-            currPosition->fX = positionRect.fRight;
-            currPosition->fY = positionRect.fBottom;
-            *(reinterpret_cast<SkColor*>(currVertex + sizeof(SkPoint))) = color;
-            currCoords = reinterpret_cast<uint16_t*>(currVertex + coordOffset);
-            currCoords[0] = coordsRectR;
-            currCoords[1] = coordsRectB;
-            currVertex += vertexStride;
-        }
-
-        blobVertices += 4 * vertexStride;
-    }
-}
-
 void GrAtlasTextOp::onPrepareDraws(Target* target) {
     auto resourceProvider = target->resourceProvider();
 
@@ -381,8 +290,6 @@
         SkASSERT((int)subRun->vertexStride() == vertexStride);
 
         subRun->prepareGrGlyphs(target->strikeCache());
-        subRun->updateVerticesColorIfNeeded(args.fColor.toBytes_RGBA());
-        subRun->translateVerticesIfNeeded(args.fDrawMatrix, args.fDrawOrigin);
 
         // TODO4F: Preserve float colors
         GrTextBlob::VertexRegenerator regenerator(resourceProvider, subRun,
@@ -409,39 +316,10 @@
             // Update all the vertices for glyphsRegenerate glyphs.
             if (glyphsRegenerated > 0) {
                 int quadBufferIndex = totalGlyphsRegened - quadBufferBegin;
-                int subRunIndex = totalGlyphsRegened - subRunBegin;
                 auto regeneratedQuadBuffer =
                         SkTAddOffset<char>(vertices, subRun->quadOffset(quadBufferIndex));
-                if (args.fClipRect.isEmpty()) {
-                    memcpy(regeneratedQuadBuffer,
-                           subRun->quadStart(subRunIndex),
-                           glyphsRegenerated * quadSize);
-                } else {
-                    SkASSERT(!vmPerspective);
-                    clip_quads(args.fClipRect,
-                               regeneratedQuadBuffer,
-                               subRun->quadStart(subRunIndex),
-                               vertexStride,
-                               glyphsRegenerated);
-                }
-                if (fNeedsGlyphTransform && !args.fDrawMatrix.isIdentity()) {
-                    // We always do the distance field view matrix transformation after copying
-                    // rather than during blob vertex generation time in the blob as handling
-                    // successive arbitrary transformations would be complicated and accumulate
-                    // error.
-                    if (args.fDrawMatrix.hasPerspective()) {
-                        auto* pos = reinterpret_cast<SkPoint3*>(regeneratedQuadBuffer);
-                        SkMatrixPriv::MapHomogeneousPointsWithStride(
-                                args.fDrawMatrix, pos,
-                                vertexStride, pos,
-                                vertexStride,
-                                glyphsRegenerated * kVerticesPerGlyph);
-                    } else {
-                        auto* pos = reinterpret_cast<SkPoint*>(regeneratedQuadBuffer);
-                        SkMatrixPriv::MapPointsWithStride(args.fDrawMatrix, pos, vertexStride,
-                                                          glyphsRegenerated * kVerticesPerGlyph);
-                    }
-                }
+                int subRunIndex = totalGlyphsRegened - subRunBegin;
+                args.fillVertexData(regeneratedQuadBuffer, subRunIndex, glyphsRegenerated);
             }
 
             totalGlyphsRegened += glyphsRegenerated;
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 451de49..b8fb9e3 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -34,6 +34,9 @@
         SkPoint     fDrawOrigin;
         GrTextBlob::SubRun* fSubRunPtr;
         SkPMColor4f fColor;
+        std::unique_ptr<GrTextBlob::Mask3DVertex[][4]> textTargetCreateVertexData(
+                int offset, int count) const;
+        void fillVertexData(void* dst, int offset, int count) const;
     };
 
     static std::unique_ptr<GrAtlasTextOp> MakeBitmap(GrRecordingContext* context,
diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp
index d708561..5e3d928 100644
--- a/src/gpu/text/GrTextBlob.cpp
+++ b/src/gpu/text/GrTextBlob.cpp
@@ -41,18 +41,15 @@
 
 // -- GrTextBlob::SubRun ---------------------------------------------------------------------------
 GrTextBlob::SubRun::SubRun(SubRunType type, GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec,
-                           GrMaskFormat format, const SkSpan<PackedGlyphIDorGrGlyph>& glyphs,
-                           const SkSpan<char>& vertexData)
+                           GrMaskFormat format, SkRect vertexBounds,
+                           const SkSpan<VertexData>& vertexData)
         : fType{type}
         , fBlob{textBlob}
         , fMaskFormat{format}
-        , fVertexData{vertexData}
         , fStrikeSpec{strikeSpec}
-        , fCurrentColor{textBlob->fColor}
-        , fCurrentOrigin{0,0}
-        , fCurrentMatrix{textBlob->fInitialMatrix}
-        , fGlyphs{glyphs} {
-    SkASSERT(type != kTransformedPath);
+        , fVertexBounds(vertexBounds)
+        , fVertexData{vertexData} {
+    SkASSERT(fType != kTransformedPath);
     textBlob->insertSubRun(this);
 }
 
@@ -60,129 +57,242 @@
         : fType{kTransformedPath}
         , fBlob{textBlob}
         , fMaskFormat{kA8_GrMaskFormat}
-        , fVertexData{SkSpan<char>{}}
         , fStrikeSpec{strikeSpec}
-        , fCurrentColor{textBlob->fColor}
         , fPaths{}
-        , fGlyphs{SkSpan<PackedGlyphIDorGrGlyph>{}} {
+        , fVertexBounds(SkRect::MakeEmpty())
+        , fVertexData{SkSpan<VertexData>{}} {
     textBlob->insertSubRun(this);
 }
 
-static SkRect dest_rect(const SkGlyph& g, SkPoint origin) {
-    return SkRect::MakeXYWH(
-            SkIntToScalar(g.left()) + origin.x(),
-            SkIntToScalar(g.top())  + origin.y(),
-            SkIntToScalar(g.width()),
-            SkIntToScalar(g.height()));
-}
-
-static bool is_SDF(const SkGlyph& skGlyph) {
-    return skGlyph.maskFormat() == SkMask::kSDF_Format;
-}
-
-static SkRect dest_rect(const SkGlyph& g, SkPoint origin, SkScalar textScale) {
-    if (!is_SDF(g)) {
-        return SkRect::MakeXYWH(
-                SkIntToScalar(g.left())   * textScale + origin.x(),
-                SkIntToScalar(g.top())    * textScale + origin.y(),
-                SkIntToScalar(g.width())  * textScale,
-                SkIntToScalar(g.height()) * textScale);
-    } else {
-        return SkRect::MakeXYWH(
-                (SkIntToScalar(g.left()) + SK_DistanceFieldInset) * textScale + origin.x(),
-                (SkIntToScalar(g.top())  + SK_DistanceFieldInset) * textScale + origin.y(),
-                (SkIntToScalar(g.width())  - 2 * SK_DistanceFieldInset) * textScale,
-                (SkIntToScalar(g.height()) - 2 * SK_DistanceFieldInset) * textScale);
-    }
-}
-
-void GrTextBlob::SubRun::appendGlyphs(const SkZip<SkGlyphVariant, SkPoint>& drawables) {
-    SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
-    SkASSERT(!this->isPrepared());
-    PackedGlyphIDorGrGlyph* packedIDCursor = fGlyphs.data();
-    char* vertexCursor = fVertexData.data();
-    size_t vertexStride = this->vertexStride();
-    // We always write the third position component used by SDFs. If it is unused it gets
-    // overwritten. Similarly, we always write the color and the blob will later overwrite it
-    // with texture coords if it is unused.
-    size_t colorOffset = this->colorOffset();
-    for (auto [variant, pos] : drawables) {
-        SkGlyph* skGlyph = variant;
-        // Only floor the device coordinates.
-        SkRect dstRect;
-        if (!this->needsTransform()) {
-            dstRect = dest_rect(*skGlyph, pos);
-        } else {
-            dstRect = dest_rect(*skGlyph, pos, strikeToSource);
-        }
-
-        this->joinGlyphBounds(dstRect);
-
-        // V0
-        *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fLeft, dstRect.fTop, 1.f};
-        *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
-        vertexCursor += vertexStride;
-
-        // V1
-        *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fLeft, dstRect.fBottom, 1.f};
-        *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
-        vertexCursor += vertexStride;
-
-        // V2
-        *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fRight, dstRect.fTop, 1.f};
-        *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
-        vertexCursor += vertexStride;
-
-        // V3
-        *reinterpret_cast<SkPoint3*>(vertexCursor) = {dstRect.fRight, dstRect.fBottom, 1.f};
-        *reinterpret_cast<GrColor*>(vertexCursor + colorOffset) = fCurrentColor;
-        vertexCursor += vertexStride;
-
-        packedIDCursor->fPackedGlyphID = skGlyph->getPackedID();
-        packedIDCursor++;
-    }
-}
-
 void GrTextBlob::SubRun::resetBulkUseToken() { fBulkUseToken.reset(); }
 
 GrDrawOpAtlas::BulkUseTokenUpdater* GrTextBlob::SubRun::bulkUseToken() { return &fBulkUseToken; }
 GrTextStrike* GrTextBlob::SubRun::strike() const { return fStrike.get(); }
 GrMaskFormat GrTextBlob::SubRun::maskFormat() const { return fMaskFormat; }
-size_t GrTextBlob::SubRun::vertexStride() const {
-    return GetVertexStride(this->maskFormat(), this->hasW());
-}
-size_t GrTextBlob::SubRun::colorOffset() const {
-    return this->hasW() ? offsetof(Mask3DVertex, color) : offsetof(Mask2DVertex, color);
-}
 
-size_t GrTextBlob::SubRun::texCoordOffset() const {
-    switch (fMaskFormat) {
+size_t GrTextBlob::SubRun::vertexStride() const {
+    switch (this->maskFormat()) {
         case kA8_GrMaskFormat:
-            return this->hasW() ? offsetof(Mask3DVertex, atlasPos)
-                                : offsetof(Mask2DVertex, atlasPos);
+            return this->hasW() ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex);
         case kARGB_GrMaskFormat:
-            return this->hasW() ? offsetof(ARGB3DVertex, atlasPos)
-                                : offsetof(ARGB2DVertex, atlasPos);
+            return this->hasW() ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
         default:
             SkASSERT(!this->hasW());
-            return offsetof(Mask2DVertex, atlasPos);
+            return sizeof(Mask2DVertex);
     }
-}
-
-char* GrTextBlob::SubRun::quadStart(size_t index) const {
-    return SkTAddOffset<char>(fVertexData.data(), this->quadOffset(index));
+    SkUNREACHABLE;
 }
 
 size_t GrTextBlob::SubRun::quadOffset(size_t index) const {
     return index * kVerticesPerGlyph * this->vertexStride();
 }
 
-int GrTextBlob::SubRun::glyphCount() const {
-    return fGlyphs.count();
+template <typename Rect>
+static auto ltbr(const Rect& r) {
+    return std::make_tuple(r.left(), r.top(), r.right(), r.bottom());
 }
 
-void GrTextBlob::SubRun::joinGlyphBounds(const SkRect& glyphBounds) {
-    fVertexBounds.joinNonEmptyArg(glyphBounds);
+void GrTextBlob::SubRun::fillVertexData(
+        void *vertexDst, int offset, int count,
+        GrColor color, const SkMatrix& drawMatrix, SkPoint drawOrigin, SkIRect clip) const {
+
+    SkMatrix matrix = drawMatrix;
+    matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
+
+    auto transformed2D = [&](auto dst, SkScalar dstPadding, SkScalar srcPadding) {
+        SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
+        SkPoint inset = {dstPadding, dstPadding};
+        for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) {
+            auto[glyph, pos, rect] = vertexData;
+            auto [l, t, r, b] = rect;
+            SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos,
+                    sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos;
+            SkPoint lt = matrix.mapXY(sLT.x(), sLT.y()),
+                    lb = matrix.mapXY(sLT.x(), sRB.y()),
+                    rt = matrix.mapXY(sRB.x(), sLT.y()),
+                    rb = matrix.mapXY(sRB.x(), sRB.y());
+            auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(srcPadding);
+            quad[0] = {lt, color, {al, at}};  // L,T
+            quad[1] = {lb, color, {al, ab}};  // L,B
+            quad[2] = {rt, color, {ar, at}};  // R,T
+            quad[3] = {rb, color, {ar, ab}};  // R,B
+        }
+    };
+
+    auto transformed3D = [&](auto dst, SkScalar dstPadding, SkScalar srcPadding) {
+        SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
+        SkPoint inset = {dstPadding, dstPadding};
+        auto mapXYZ = [&](SkScalar x, SkScalar y) {
+            SkPoint pt{x, y};
+            SkPoint3 result;
+            matrix.mapHomogeneousPoints(&result, &pt, 1);
+            return result;
+        };
+        for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) {
+            auto[glyph, pos, rect] = vertexData;
+            auto [l, t, r, b] = rect;
+            SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos,
+                    sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos;
+            SkPoint3 lt = mapXYZ(sLT.x(), sLT.y()),
+                     lb = mapXYZ(sLT.x(), sRB.y()),
+                     rt = mapXYZ(sRB.x(), sLT.y()),
+                     rb = mapXYZ(sRB.x(), sRB.y());
+            auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(srcPadding);
+            quad[0] = {lt, color, {al, at}};  // L,T
+            quad[1] = {lb, color, {al, ab}};  // L,B
+            quad[2] = {rt, color, {ar, at}};  // R,T
+            quad[3] = {rb, color, {ar, ab}};  // R,B
+        }
+    };
+
+    auto direct2D = [&](auto dst, SkIRect* clip) {
+        // Rectangles in device space
+        SkPoint originInDeviceSpace = matrix.mapXY(0, 0);
+        for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) {
+            auto[glyph, pos, rect] = vertexData;
+            const auto[l, t, r, b] = rect;
+            const auto[fx, fy] = pos + originInDeviceSpace;
+            auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(0);
+            if (clip == nullptr) {
+                SkScalar dx = SkScalarRoundToScalar(fx),
+                         dy = SkScalarRoundToScalar(fy);
+                auto[dl, dt, dr, db] = SkRect::MakeLTRB(l + dx, t + dy, r + dx, b + dy);
+                quad[0] = {{dl, dt}, color, {al, at}};  // L,T
+                quad[1] = {{dl, db}, color, {al, ab}};  // L,B
+                quad[2] = {{dr, dt}, color, {ar, at}};  // R,T
+                quad[3] = {{dr, db}, color, {ar, ab}};  // R,B
+            } else {
+                int dx = SkScalarRoundToInt(fx),
+                    dy = SkScalarRoundToInt(fy);
+                SkIRect devIRect = SkIRect::MakeLTRB(l + dx, t + dy, r + dx, b + dy);
+                SkScalar dl, dt, dr, db;
+                uint16_t tl, tt, tr, tb;
+                if (!clip->containsNoEmptyCheck(devIRect)) {
+                    if (SkIRect clipped; clipped.intersect(devIRect, *clip)) {
+                        int lD = clipped.left() - devIRect.left();
+                        int tD = clipped.top() - devIRect.top();
+                        int rD = clipped.right() - devIRect.right();
+                        int bD = clipped.bottom() - devIRect.bottom();
+                        int indexLT, indexRB;
+                        std::tie(dl, dt, dr, db) = ltbr(clipped);
+                        std::tie(tl, tt, indexLT) =
+                                GrDrawOpAtlas::UnpackIndexFromTexCoords(al, at);
+                        std::tie(tr, tb, indexRB) =
+                                GrDrawOpAtlas::UnpackIndexFromTexCoords(ar, ab);
+                        std::tie(tl, tt) =
+                                GrDrawOpAtlas::PackIndexInTexCoords(tl + lD, tt + tD, indexLT);
+                        std::tie(tr, tb) =
+                                GrDrawOpAtlas::PackIndexInTexCoords(tr + rD, tb + bD, indexRB);
+                    } else {
+                        // TODO: omit generating any vertex data for fully clipped glyphs ?
+                        std::tie(dl, dt, dr, db) = std::make_tuple(0, 0, 0, 0);
+                        std::tie(tl, tt, tr, tb) = std::make_tuple(0, 0, 0, 0);
+                    }
+
+                } else {
+                    std::tie(dl, dt, dr, db) = ltbr(devIRect);
+                    std::tie(tl, tt, tr, tb) = std::tie(al, at, ar, ab);
+                }
+                quad[0] = {{dl, dt}, color, {tl, tt}};  // L,T
+                quad[1] = {{dl, db}, color, {tl, tb}};  // L,B
+                quad[2] = {{dr, dt}, color, {tr, tt}};  // R,T
+                quad[3] = {{dr, db}, color, {tr, tb}};  // R,B
+            }
+        }
+    };
+
+    switch (fType) {
+        case kDirectMask: {
+            if (clip.isEmpty()) {
+                if (this->maskFormat() != kARGB_GrMaskFormat) {
+                    using Quad = Mask2DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    direct2D((Quad*) vertexDst, nullptr);
+                } else {
+                    using Quad = ARGB2DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    direct2D((Quad*) vertexDst, nullptr);
+                }
+            } else {
+                if (this->maskFormat() != kARGB_GrMaskFormat) {
+                    using Quad = Mask2DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    direct2D((Quad*) vertexDst, &clip);
+                } else {
+                    using Quad = ARGB2DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    direct2D((Quad*) vertexDst, &clip);
+                }
+            }
+            break;
+        }
+        case kTransformedMask: {
+            if (!this->hasW()) {
+                if (this->maskFormat() == GrMaskFormat::kARGB_GrMaskFormat) {
+                    using Quad = ARGB2DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    transformed2D((Quad*) vertexDst, 0, 1);
+                } else {
+                    using Quad = Mask2DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    transformed2D((Quad*) vertexDst, 0, 1);
+                }
+            } else {
+                if (this->maskFormat() == GrMaskFormat::kARGB_GrMaskFormat) {
+                    using Quad = ARGB3DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    transformed3D((Quad*) vertexDst, 0, 1);
+                } else {
+                    using Quad = Mask3DVertex[4];
+                    SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                    transformed3D((Quad*) vertexDst, 0, 1);
+                }
+            }
+            break;
+        }
+        case kTransformedSDFT: {
+            if (!this->hasW()) {
+                using Quad = Mask2DVertex[4];
+                SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                transformed2D((Quad*) vertexDst, SK_DistanceFieldInset, SK_DistanceFieldInset);
+            } else {
+                using Quad = Mask3DVertex[4];
+                SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
+                transformed3D((Quad*) vertexDst, SK_DistanceFieldInset, SK_DistanceFieldInset);
+            }
+            break;
+        }
+        case kTransformedPath:
+            SK_ABORT("Paths don't generate vertex data.");
+    }
+}
+
+// Note: this method is only used with SkAtlasTextTarget. The SkAtlasTextTarget only uses SDF,
+// and does the rectangle transforms on the GPU. For the normal text execution path see
+// fillVertexData.
+void GrTextBlob::SubRun::fillTextTargetVertexData(
+        Mask3DVertex vertexDst[][4], int offset, int count, GrColor color, SkPoint origin) const {
+    SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
+    SkPoint inset = {SK_DistanceFieldInset, SK_DistanceFieldInset};
+    for (auto[dst, vertexData] : SkMakeZip(vertexDst, fVertexData.subspan(offset, count))) {
+        auto[glyph, pos, rect] = vertexData;
+        auto [l, t, r, b] = rect;
+        SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos + origin,
+                sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos + origin;
+        SkPoint3 lt = SkPoint3{sLT.x(), sLT.y(), 1.f},
+                 lb = SkPoint3{sLT.x(), sRB.y(), 1.f},
+                 rt = SkPoint3{sRB.x(), sLT.y(), 1.f},
+                 rb = SkPoint3{sRB.x(), sRB.y(), 1.f};
+        auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(SK_DistanceFieldInset);
+        dst[0] = {lt, color, {al, at}};  // L,T
+        dst[1] = {lb, color, {al, ab}};  // L,B
+        dst[2] = {rt, color, {ar, at}};  // R,T
+        dst[3] = {rb, color, {ar, ab}};  // R,B
+    }
+}
+
+int GrTextBlob::SubRun::glyphCount() const {
+    return fVertexData.count();
 }
 
 bool GrTextBlob::SubRun::drawAsDistanceFields() const { return fType == kTransformedSDFT; }
@@ -210,88 +320,8 @@
 
     fStrike = fStrikeSpec.findOrCreateGrStrike(strikeCache);
 
-    for (auto& tmp : fGlyphs) {
-        tmp.fGrGlyph = fStrike->getGlyph(tmp.fPackedGlyphID);
-    }
-}
-
-void GrTextBlob::SubRun::translateVerticesIfNeeded(
-        const SkMatrix& drawMatrix, SkPoint drawOrigin) {
-    SkVector translation;
-    if (this->needsTransform()) {
-        // If transform is needed, then the vertices are in source space, calculate the source
-        // space translation.
-        translation = drawOrigin - fCurrentOrigin;
-        fCurrentOrigin = drawOrigin;
-    } else {
-        // Calculate the translation in destination space.
-        SkPoint newOrigin = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y());
-        translation = newOrigin - fCurrentOrigin;
-        fCurrentOrigin = newOrigin;
-    }
-
-    if (translation != SkPoint{0, 0}) {
-        size_t vertexStride = this->vertexStride();
-        for (size_t quad = 0; quad < fGlyphs.size(); quad++) {
-            SkPoint* vertexCursor = reinterpret_cast<SkPoint*>(quadStart(quad));
-            for (int i = 0; i < 4; ++i) {
-                if (this->needsTransform()) {
-                    *vertexCursor += translation;
-                } else {
-                    // This should result in an integer, but floating point is not accurate. This
-                    // result should be very close to an integer; round to an integer.
-                    *vertexCursor = {SkScalarRoundToScalar(vertexCursor->x() + translation.x()),
-                                     SkScalarRoundToScalar(vertexCursor->y() + translation.y())};
-                }
-                vertexCursor = SkTAddOffset<SkPoint>(vertexCursor, vertexStride);
-            }
-        }
-        fCurrentMatrix = drawMatrix;
-    }
-}
-
-void GrTextBlob::SubRun::updateVerticesColorIfNeeded(GrColor newColor) {
-    if (this->maskFormat() != kARGB_GrMaskFormat && fCurrentColor != newColor) {
-        size_t vertexStride = this->vertexStride();
-        size_t colorOffset = this->colorOffset();
-        for (size_t quad = 0; quad < fGlyphs.size(); quad++) {
-            GrColor* colorCursor = SkTAddOffset<GrColor>(quadStart(quad), colorOffset);
-            for (int i = 0; i < 4; ++i) {
-                *colorCursor = newColor;
-                colorCursor = SkTAddOffset<GrColor>(colorCursor, vertexStride);
-            }
-        }
-        this->fCurrentColor = newColor;
-    }
-}
-
-void GrTextBlob::SubRun::updateTexCoords(int begin, int end) {
-    SkASSERT(this->isPrepared());
-
-    const size_t vertexStride = this->vertexStride();
-    const size_t texCoordOffset = this->texCoordOffset();
-    char* vertex = this->quadStart(begin);
-    uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-    for (int i = begin; i < end; i++) {
-        GrGlyph* glyph = this->fGlyphs[i].fGrGlyph;
-        SkASSERT(glyph != nullptr);
-
-        int pad = this->drawAsDistanceFields() ? SK_DistanceFieldInset
-                                               : (this->needsPadding() ? 1 : 0);
-        std::array<uint16_t, 4> uvs = glyph->fAtlasLocator.getUVs(pad);
-
-        textureCoords[0] = uvs[0];
-        textureCoords[1] = uvs[1];
-        textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
-        textureCoords[0] = uvs[0];
-        textureCoords[1] = uvs[3];
-        textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
-        textureCoords[0] = uvs[2];
-        textureCoords[1] = uvs[1];
-        textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
-        textureCoords[0] = uvs[2];
-        textureCoords[1] = uvs[3];
-        textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
+    for (auto& tmp : fVertexData) {
+        tmp.glyph.grGlyph = fStrike->getGlyph(tmp.glyph.packedGlyphID);
     }
 }
 
@@ -313,7 +343,7 @@
 }
 
 GrGlyph* GrTextBlob::SubRun::grGlyph(int i) const {
-    return fGlyphs[i].fGrGlyph;
+    return fVertexData[i].glyph.grGlyph;
 }
 
 void GrTextBlob::SubRun::setUseLCDText(bool useLCDText) { fFlags.useLCDText = useLCDText; }
@@ -333,29 +363,11 @@
                                    const SkMatrix& drawMatrix,
                                    GrColor color,
                                    bool forceWForDistanceFields) {
-
-    static_assert(sizeof(ARGB2DVertex) <= sizeof(Mask2DVertex));
-    static_assert(alignof(ARGB2DVertex) <= alignof(Mask2DVertex));
-    size_t quadSize = sizeof(Mask2DVertex) * kVerticesPerGlyph;
-    if (drawMatrix.hasPerspective() || forceWForDistanceFields) {
-        static_assert(sizeof(ARGB3DVertex) <= sizeof(Mask3DVertex));
-        static_assert(alignof(ARGB3DVertex) <= alignof(Mask3DVertex));
-        quadSize = sizeof(Mask3DVertex) * kVerticesPerGlyph;
-    }
-
-    // We can use the alignment of SDFT3DVertex as a proxy for all Vertex alignments.
-    static_assert(alignof(Mask3DVertex) >= alignof(Mask2DVertex));
-    // Assume there is no padding needed between glyph pointers and vertices.
-    static_assert(alignof(GrGlyph*) >= alignof(Mask3DVertex));
-
-    // In the arena, the layout is GrGlyph*... | SDFT3DVertex... | SubRun, so there is no padding
-    // between GrGlyph* and SDFT3DVertex, but padding is needed between the Mask2DVertex array
-    // and the SubRun.
-    size_t vertexToSubRunPadding = alignof(Mask3DVertex) - alignof(SubRun);
-    size_t arenaSize =
-            sizeof(GrGlyph*) * glyphRunList.totalGlyphCount()
-          + quadSize * glyphRunList.totalGlyphCount()
-          + glyphRunList.runCount() * (sizeof(SubRun) + vertexToSubRunPadding);
+    // The difference in alignment from the storage of VertexData to SubRun;
+    constexpr size_t alignDiff = alignof(SubRun) - alignof(SubRun::VertexData);
+    constexpr size_t vertexDataToSubRunPadding = alignDiff > 0 ? alignDiff : 0;
+    size_t arenaSize = sizeof(SubRun::VertexData) *  glyphRunList.totalGlyphCount()
+                     + glyphRunList.runCount() * (sizeof(SubRun) + vertexDataToSubRunPadding);
 
     size_t allocationSize = sizeof(GrTextBlob) + arenaSize;
 
@@ -398,18 +410,6 @@
     fMinMaxScale = std::min(scaledMax, fMinMaxScale);
 }
 
-size_t GrTextBlob::GetVertexStride(GrMaskFormat maskFormat, bool hasWCoord) {
-    switch (maskFormat) {
-        case kA8_GrMaskFormat:
-            return hasWCoord ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex);
-        case kARGB_GrMaskFormat:
-            return hasWCoord ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
-        default:
-            SkASSERT(!hasWCoord);
-            return sizeof(Mask2DVertex);
-    }
-}
-
 bool GrTextBlob::mustRegenerate(const SkPaint& paint, bool anyRunHasSubpixelPosition,
                                 const SkMaskFilterBase::BlurRec& blurRec,
                                 const SkMatrix& drawMatrix, SkPoint drawOrigin) {
@@ -626,18 +626,26 @@
                                            const SkZip<SkGlyphVariant, SkPoint>& drawables,
                                            const SkStrikeSpec& strikeSpec,
                                            GrMaskFormat format) {
-    SkSpan<SubRun::PackedGlyphIDorGrGlyph> glyphs{
-       fAlloc.makeArrayDefault<SubRun::PackedGlyphIDorGrGlyph>(drawables.size()), drawables.size()};
-    bool hasW = this->hasW(type);
+    size_t vertexCount = drawables.size();
+    using Data = SubRun::VertexData;
+    SkRect bounds = SkRectPriv::MakeLargestInverted();
+    auto initializer = [&, strikeToSource=strikeSpec.strikeToSourceRatio()](size_t i) {
+        auto [variant, pos] = drawables[i];
+        SkGlyph* skGlyph = variant;
+        int16_t l = skGlyph->left();
+        int16_t t = skGlyph->top();
+        int16_t r = l + skGlyph->width();
+        int16_t b = t + skGlyph->height();
+        SkPoint lt = SkPoint::Make(l, t) * strikeToSource + pos,
+                rb = SkPoint::Make(r, b) * strikeToSource + pos;
+        bounds.joinNonEmptyArg(SkRect::MakeLTRB(lt.x(), lt.y(), rb.x(), rb.y()));
+        return Data{{skGlyph->getPackedID()}, pos, {l, t, r, b}};
+    };
 
-    SkASSERT(!fInitialMatrix.hasPerspective() || hasW);
+    SkSpan<Data> vertexData{
+        fAlloc.makeInitializedArray<Data>(vertexCount, initializer), vertexCount};
 
-    size_t vertexDataSize = drawables.size() * GetVertexStride(format, hasW) * kVerticesPerGlyph;
-    SkSpan<char> vertexData{fAlloc.makeArrayDefault<char>(vertexDataSize), vertexDataSize};
-
-    SubRun* subRun = fAlloc.make<SubRun>(type, this, strikeSpec, format, glyphs, vertexData);
-
-    subRun->appendGlyphs(drawables);
+    SubRun* subRun = fAlloc.make<SubRun>(type, this, strikeSpec, format, bounds, vertexData);
 
     return subRun;
 }
@@ -966,9 +974,6 @@
     }
     int glyphsPlacedInAtlas = i - begin;
 
-    // Update the quads with the new atlas coordinates.
-    fSubRun->updateTexCoords(begin, begin + glyphsPlacedInAtlas);
-
     return {code != GrDrawOpAtlas::ErrorCode::kError, glyphsPlacedInAtlas};
 }
 
@@ -987,6 +992,7 @@
             // updateTextureCoordinates may have changed it.
             fSubRun->fAtlasGeneration = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
         }
+
         return {success, glyphsPlacedInAtlas};
     } else {
         // The atlas hasn't changed, so our texture coordinates are still valid.
diff --git a/src/gpu/text/GrTextBlob.h b/src/gpu/text/GrTextBlob.h
index f5d6108..9c20be6 100644
--- a/src/gpu/text/GrTextBlob.h
+++ b/src/gpu/text/GrTextBlob.h
@@ -124,8 +124,6 @@
     void setHasBitmap();
     void setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax);
 
-    static size_t GetVertexStride(GrMaskFormat maskFormat, bool hasWCoord);
-
     bool mustRegenerate(const SkPaint&, bool, const SkMaskFilterBase::BlurRec& blurRec,
                         const SkMatrix& drawMatrix, SkPoint drawOrigin);
 
@@ -321,14 +319,16 @@
 // glyphs that are included in them.
 class GrTextBlob::SubRun {
 public:
-    // Within a glyph-based subRun, the glyphs are initially recorded as SkPackedGlyphs. At
-    // flush time they are then converted to GrGlyph's (via the GrTextStrike). Once converted
-    // they are never converted back.
-    union PackedGlyphIDorGrGlyph {
-        PackedGlyphIDorGrGlyph() {}
-
-        SkPackedGlyphID fPackedGlyphID;
-        GrGlyph*        fGrGlyph;
+    struct VertexData {
+        union {
+            // Initially, filled with packed id, but changed to GrGlyph* in the onPrepare stage.
+            SkPackedGlyphID packedGlyphID;
+            GrGlyph* grGlyph;
+        } glyph;
+        const SkPoint pos;
+        // The rectangle of the glyphs in strike space. But, for kDirectMask this also implies a
+        // device space rect.
+        GrIRect16 rect;
     };
 
     // SubRun for masks
@@ -336,14 +336,12 @@
            GrTextBlob* textBlob,
            const SkStrikeSpec& strikeSpec,
            GrMaskFormat format,
-           const SkSpan<PackedGlyphIDorGrGlyph>& glyphs,
-           const SkSpan<char>& vertexData);
+           SkRect vertexBounds,
+           const SkSpan<VertexData>& vertexData);
 
     // SubRun for paths
     SubRun(GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec);
 
-    void appendGlyphs(const SkZip<SkGlyphVariant, SkPoint>& drawables);
-
     // TODO when this object is more internal, drop the privacy
     void resetBulkUseToken();
     GrDrawOpAtlas::BulkUseTokenUpdater* bulkUseToken();
@@ -352,15 +350,21 @@
     GrMaskFormat maskFormat() const;
 
     size_t vertexStride() const;
-    size_t colorOffset() const;
-    size_t texCoordOffset() const;
-    char* quadStart(size_t index) const;
     size_t quadOffset(size_t index) const;
+    void fillVertexData(
+            void* vertexDst, int offset, int count,
+            GrColor color, const SkMatrix& drawMatrix, SkPoint drawOrigin,
+            SkIRect clip) const;
+
+    void fillTextTargetVertexData(
+            Mask3DVertex vertexDst[][4],
+            int offset,
+            int count,
+            GrColor color,
+            SkPoint origin) const;
 
     int glyphCount() const;
 
-    void joinGlyphBounds(const SkRect& glyphBounds);
-
     bool drawAsDistanceFields() const;
     bool drawAsPaths() const;
     bool needsTransform() const;
@@ -371,10 +375,6 @@
     // has 'prepareGrGlyphs' been called (i.e., can the GrGlyphs be accessed) ?
     SkDEBUGCODE(bool isPrepared() const { return SkToBool(fStrike); })
 
-    void translateVerticesIfNeeded(const SkMatrix& drawMatrix, SkPoint drawOrigin);
-    void updateVerticesColorIfNeeded(GrColor newColor);
-    void updateTexCoords(int begin, int end);
-
     // The rectangle that surrounds all the glyph bounding boxes in device space.
     SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const;
 
@@ -392,28 +392,23 @@
     const SubRunType fType;
     GrTextBlob* const fBlob;
     const GrMaskFormat fMaskFormat;
-    const SkSpan<char> fVertexData;
     const SkStrikeSpec fStrikeSpec;
     sk_sp<GrTextStrike> fStrike;
     struct {
         bool useLCDText:1;
         bool antiAliased:1;
-    } fFlags{false, false};
-    GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
+    } fFlags {false, false};
     uint64_t fAtlasGeneration{GrDrawOpAtlas::kInvalidAtlasGeneration};
-    GrColor fCurrentColor;
-    // If the vertex data needTransform(), then fCurrentOrigin is in source space else it is in
-    // device space.
-    SkPoint fCurrentOrigin;
-    SkMatrix fCurrentMatrix;
     std::vector<PathGlyph> fPaths;
+
 private:
     bool hasW() const;
 
-    const SkSpan<PackedGlyphIDorGrGlyph> fGlyphs;
+    GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
     // The vertex bounds in device space if needsTransform() is false, otherwise the bounds in
     // source space. The bounds are the joined rectangles of all the glyphs.
-    SkRect fVertexBounds = SkRectPriv::MakeLargestInverted();
+    const SkRect fVertexBounds;
+    const SkSpan<VertexData> fVertexData;
 };  // SubRun
 
 #endif  // GrTextBlob_DEFINED