direct mask biased to (0,0)

Create mask rectangles in device space.
But, instead of offsetting to the drawing text blob origin
offset to 0,0 to simplify mapping from source space to
device space.

Bug: skia:10251

Change-Id: Ic637eb78879bcfae7e7944053d67d9eaef8490cc
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/290133
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/core/SkGlyphBuffer.cpp b/src/core/SkGlyphBuffer.cpp
index 621e082..6a8e0c5 100644
--- a/src/core/SkGlyphBuffer.cpp
+++ b/src/core/SkGlyphBuffer.cpp
@@ -40,7 +40,7 @@
     SkDEBUGCODE(fPhase = kInput);
 }
 
-void SkDrawableGlyphBuffer::startDevice(
+void SkDrawableGlyphBuffer::startBitmapDevice(
         const SkZip<const SkGlyphID, const SkPoint>& source,
         SkPoint origin, const SkMatrix& viewMatrix,
         const SkGlyphPositionRoundingSpec& roundingSpec) {
@@ -68,6 +68,44 @@
     SkDEBUGCODE(fPhase = kInput);
 }
 
+void SkDrawableGlyphBuffer::startGPUDevice(
+        const SkZip<const SkGlyphID, const SkPoint>& source,
+        SkPoint origin, const SkMatrix& viewMatrix,
+        const SkGlyphPositionRoundingSpec& roundingSpec) {
+    fInputSize = source.size();
+    fDrawableSize = 0;
+
+    // Map the positions including subpixel position.
+    auto positions = source.get<1>();
+    SkMatrix matrix = viewMatrix;
+    matrix.preTranslate(origin.x(), origin.y());
+
+    // Q = [M][T](0,0).
+    SkPoint Q = matrix.mapXY(0, 0);
+    SkPoint halfSampleFreq = roundingSpec.halfAxisSampleFreq;
+    matrix.postTranslate(halfSampleFreq.x(), halfSampleFreq.y());
+    matrix.mapPoints(fPositions, positions.data(), positions.size());
+
+    // Mask for controlling axis alignment.
+    SkIPoint mask = roundingSpec.ignorePositionFieldMask;
+
+    // Convert glyph ids and positions to packed glyph ids.
+    SkZip<const SkGlyphID, const SkPoint> withMappedPos =
+            SkMakeZip(source.get<0>(), fPositions.get());
+    SkGlyphVariant* packedIDCursor = fMultiBuffer;
+    for (auto [glyphID, pos] : withMappedPos) {
+        *packedIDCursor++ = SkPackedGlyphID{glyphID, pos, mask};
+    }
+
+    for (SkPoint& pos : SkSpan<SkPoint>(fPositions, source.size())) {
+        SkPoint P = SkPoint::Make(SkScalarFloorToScalar(pos.x()), SkScalarFloorToScalar(pos.y()));
+        pos = P - Q;
+    }
+
+    SkDEBUGCODE(fPhase = kInput);
+}
+
+
 void SkDrawableGlyphBuffer::reset() {
     SkDEBUGCODE(fPhase = kReset);
     if (fMaxSize > 200) {
diff --git a/src/core/SkGlyphBuffer.h b/src/core/SkGlyphBuffer.h
index d0d1fdd..8458afd 100644
--- a/src/core/SkGlyphBuffer.h
+++ b/src/core/SkGlyphBuffer.h
@@ -148,7 +148,29 @@
     void startSource(const SkZip<const SkGlyphID, const SkPoint>& source);
 
     // Load the buffer with SkPackedGlyphIDs and positions using the device transform.
-    void startDevice(
+    void startBitmapDevice(
+            const SkZip<const SkGlyphID, const SkPoint>& source,
+            SkPoint origin, const SkMatrix& viewMatrix,
+            const SkGlyphPositionRoundingSpec& roundingSpec);
+
+    // Load the buffer with SkPackedGlyphIDs, calculating positions so they can be constant.
+    //
+    // A final device position is computed in the following manner:
+    //  [x,y] = Floor[M][T][x',y']^t
+    // M is complicated but includes the rounding offsets for subpixel positioning.
+    // T is the translation matrix derived from the text blob origin.
+    // The final position is {Floor(x), Floor(y)}. If we want to move this position around in
+    // device space given a start origin T in source space and a end position T' in source space
+    // and new device matrix M', we need to calculate a suitable device space translation V. We
+    // know that V must be integer.
+    // V = [M'][T'](0,0)^t - [M][T](0,0)^t.
+    // V = Q' - Q
+    // So all the positions Ps are translated by V to translate from T to T' in source space. We can
+    // generate Ps such that we just need to add any Q' to the constant Ps to get a final positions.
+    // So, a single point P = {Floor(x)-Q_x, Floor(y)-Q_y}; this does not have to be integer.
+    // This allows positioning to be P + Q', which given ideal numbers would be an integer. Since
+    // the addition is done with floating point, it must be rounded.
+    void startGPUDevice(
             const SkZip<const SkGlyphID, const SkPoint>& source,
             SkPoint origin, const SkMatrix& viewMatrix,
             const SkGlyphPositionRoundingSpec& roundingSpec);
diff --git a/src/core/SkGlyphRunPainter.cpp b/src/core/SkGlyphRunPainter.cpp
index 22c0d02..9db7bfc 100644
--- a/src/core/SkGlyphRunPainter.cpp
+++ b/src/core/SkGlyphRunPainter.cpp
@@ -124,7 +124,7 @@
 
             auto strike = strikeSpec.findOrCreateStrike();
 
-            fDrawable.startDevice(
+            fDrawable.startBitmapDevice(
                     fRejects.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
             strike->prepareForDrawingMasksCPU(&fDrawable);
             bitmapDevice->paintMasks(&fDrawable, runPaint);
@@ -189,7 +189,7 @@
 
             SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
 
-            fDrawable.startDevice(fRejects.source(), origin, drawMatrix, strike->roundingSpec());
+            fDrawable.startGPUDevice(fRejects.source(), origin, drawMatrix, strike->roundingSpec());
             strike->prepareForMaskDrawing(&fDrawable, &fRejects);
             fRejects.flipRejectsToSource();
 
diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp
index 00d2002..f963bde 100644
--- a/src/gpu/text/GrTextBlob.cpp
+++ b/src/gpu/text/GrTextBlob.cpp
@@ -50,7 +50,7 @@
         , fVertexData{vertexData}
         , fStrikeSpec{strikeSpec}
         , fCurrentColor{textBlob->fColor}
-        , fCurrentOrigin{this->needsTransform() ? SkPoint{0, 0} : textBlob->fInitialOrigin}
+        , fCurrentOrigin{0,0}
         , fCurrentMatrix{textBlob->fInitialMatrix} {
     SkASSERT(type != kTransformedPath);
     textBlob->insertSubRun(this);
@@ -111,7 +111,6 @@
         // Only floor the device coordinates.
         SkRect dstRect;
         if (!this->needsTransform()) {
-            pos = {SkScalarFloorToScalar(pos.x()), SkScalarFloorToScalar(pos.y())};
             dstRect = dest_rect(*skGlyph, pos);
         } else {
             dstRect = dest_rect(*skGlyph, pos, strikeToSource);
@@ -142,15 +141,6 @@
         packedIDCursor->fPackedGlyphID = skGlyph->getPackedID();
         packedIDCursor++;
     }
-
-    if (!this->needsTransform()) {
-        // Use the negative initial origin to make the fVertexBounds {0, 0} based.
-        SkPoint pt = fBlob->fInitialOrigin;
-
-        // If the box is in device space, then transform the source space origin to device space.
-        pt = fBlob->fInitialMatrix.mapXY(pt.x(), pt.y());
-        fVertexBounds.offset(-pt);
-    }
 }
 
 void GrTextBlob::SubRun::resetBulkUseToken() { fBulkUseToken.reset(); }
@@ -228,19 +218,12 @@
         // 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 source space to a translation in device space. Calculate
-        // the translation by mapping (0, 0) through both the current matrix, and the draw
-        // matrix, and taking the difference.
-        SkMatrix currentMatrix{fCurrentMatrix};
-        currentMatrix.preTranslate(fCurrentOrigin.x(), fCurrentOrigin.y());
-        SkPoint currentDeviceOrigin{0, 0};
-        currentMatrix.mapPoints(&currentDeviceOrigin, 1);
-        SkMatrix completeDrawMatrix{drawMatrix};
-        completeDrawMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
-        SkPoint drawDeviceOrigin{0, 0};
-        completeDrawMatrix.mapPoints(&drawDeviceOrigin, 1);
-        translation = drawDeviceOrigin - currentDeviceOrigin;
+        // Calculate the translation in destination space.
+        SkPoint newOrigin = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y());
+        translation = newOrigin - fCurrentOrigin;
+        fCurrentOrigin = newOrigin;
     }
 
     if (translation != SkPoint{0, 0}) {
@@ -248,12 +231,18 @@
         for (size_t quad = 0; quad < fGlyphs.size(); quad++) {
             SkPoint* vertexCursor = reinterpret_cast<SkPoint*>(quadStart(quad));
             for (int i = 0; i < 4; ++i) {
-                *vertexCursor += translation;
+                if (this-needsTransform()) {
+                    *vertexCursor += translation;
+                } else {
+                    // This should result in an integer, but floating point is not accurate. This
+                    // can be off upto an ulp. Round to integer.
+                    *vertexCursor = {SkScalarRoundToScalar(vertexCursor->x() + translation.x()),
+                                     SkScalarRoundToScalar(vertexCursor->y() + translation.y())};
+                }
                 vertexCursor = SkTAddOffset<SkPoint>(vertexCursor, vertexStride);
             }
         }
         fCurrentMatrix = drawMatrix;
-        fCurrentOrigin = drawOrigin;
     }
 }
 
@@ -529,7 +518,7 @@
             if (!needsExactCTM) {
                 for (const auto& pathPos : subRun->fPaths) {
                     const SkPath& path = pathPos.fPath;
-                    const SkPoint pos = pathPos.fOrigin;                    // Transform the glyph to source space.
+                    const SkPoint pos = pathPos.fOrigin;  // Transform the glyph to source space.
                     SkMatrix pathMatrix = strikeToSource;
                     pathMatrix.postTranslate(pos.x(), pos.y());
                     SkPreConcatMatrixProvider strikeToDevice(deviceMatrix, pathMatrix);
diff --git a/src/gpu/text/GrTextBlob.h b/src/gpu/text/GrTextBlob.h
index 98f813a..c9b56d8 100644
--- a/src/gpu/text/GrTextBlob.h
+++ b/src/gpu/text/GrTextBlob.h
@@ -393,6 +393,8 @@
     GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
     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;
diff --git a/tests/SkGlyphBufferTest.cpp b/tests/SkGlyphBufferTest.cpp
index 3c3c1a3..77c4c74 100644
--- a/tests/SkGlyphBufferTest.cpp
+++ b/tests/SkGlyphBufferTest.cpp
@@ -172,7 +172,7 @@
         drawable.ensureSize(100);
         SkMatrix matrix = SkMatrix::MakeScale(0.5);
         SkGlyphPositionRoundingSpec rounding{true, kX_SkAxisAlignment};
-        drawable.startDevice(source, {100, 100}, matrix, rounding);
+        drawable.startBitmapDevice(source, {100, 100}, matrix, rounding);
         for (auto [i, packedID, pos] : SkMakeEnumerate(drawable.input())) {
             REPORTER_ASSERT(reporter, glyphIDs[i] == packedID.packedID().glyphID());
             REPORTER_ASSERT(reporter,
diff --git a/tests/SkScalerCacheTest.cpp b/tests/SkScalerCacheTest.cpp
index 20480f4..6bc5dae 100644
--- a/tests/SkScalerCacheTest.cpp
+++ b/tests/SkScalerCacheTest.cpp
@@ -72,8 +72,8 @@
                 drawable.ensureSize(glyphCount);
                 rejects.setSource(local);
 
-                drawable.startDevice(rejects.source(), {0, 0}, SkMatrix::I(),
-                                     scalerCache.roundingSpec());
+                drawable.startBitmapDevice(rejects.source(), {0, 0}, SkMatrix::I(),
+                                           scalerCache.roundingSpec());
                 scalerCache.prepareForMaskDrawing(&drawable, &rejects);
                 rejects.flipRejectsToSource();
                 drawable.reset();