Send less transform data when drawing text with nvpr

Before this change, GrStencilAndCoverTextContext would send 6-float
affine transforms to drawPaths for every glyph. This updates it to
concat the text scale onto the context matrix, and then only send
2-float translates (or 1-float x-translates when possible).

Also adds a glyph_pos_align test to gm that exercises the newly added
codepaths, and starts ignoring a few gm tests with benign pixel diffs
until we can rebaseline.

BUG=skia:
R=bsalomon@google.com, kkinnunen@nvidia.com, jvanverth@google.com, bungeman@google.com

Author: cdalton@nvidia.com

Review URL: https://codereview.chromium.org/406523003
diff --git a/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt
index 5feb7b1..4f23204 100644
--- a/expectations/gm/ignored-tests.txt
+++ b/expectations/gm/ignored-tests.txt
@@ -49,3 +49,14 @@
 #bungeman: there's now a fontmgr on android
 fontmgr_match
 fontmgr_iter
+
+# cdalton:
+# Needs rebaselining after nvpr text translate patch
+shadertext3
+shadertext2
+glyph_pos_n_f
+glyph_pos_h_f
+glyph_pos_n_s
+glyph_pos_h_s
+glyph_pos_n_b
+glyph_pos_h_b
diff --git a/gm/glyph_pos_align.cpp b/gm/glyph_pos_align.cpp
new file mode 100644
index 0000000..9a94e75
--- /dev/null
+++ b/gm/glyph_pos_align.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkGradientShader.h"
+
+/**
+ * This test exercises drawPosTextH and drawPosText with every text align.
+ */
+static const int kWidth = 480;
+static const int kHeight = 600;
+static const SkScalar kTextHeight = 64.0f;
+static const int kMaxStringLength = 12;
+
+namespace skiagm {
+
+class GlyphPosAlignGM : public GM {
+protected:
+    virtual uint32_t onGetFlags() const SK_OVERRIDE {
+        return kSkipTiled_Flag;
+    }
+
+    virtual SkString onShortName() SK_OVERRIDE {
+        return SkString("glyph_pos_align");
+    }
+
+    virtual SkISize onISize() { return SkISize::Make(kWidth, kHeight); }
+
+    virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+        canvas->clear(SK_ColorBLACK);
+
+        SkPaint paint;
+        paint.setTextSize(kTextHeight);
+        paint.setFakeBoldText(true);
+        const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+        const SkPoint pts[] = {{0, 0}, {kWidth, kHeight}};
+        SkAutoTUnref<SkShader> grad(SkGradientShader::CreateLinear(pts, colors, NULL,
+                                                                   SK_ARRAY_COUNT(colors),
+                                                                   SkShader::kMirror_TileMode));
+        paint.setShader(grad);
+
+
+        paint.setTextAlign(SkPaint::kRight_Align);
+        drawTestCase(canvas, "Right Align", kTextHeight, paint);
+
+        paint.setTextAlign(SkPaint::kCenter_Align);
+        drawTestCase(canvas, "Center Align", 4 * kTextHeight, paint);
+
+        paint.setTextAlign(SkPaint::kLeft_Align);
+        drawTestCase(canvas, "Left Align", 7 * kTextHeight, paint);
+    }
+
+    void drawTestCase(SkCanvas* canvas, const char* text, SkScalar y, const SkPaint& paint) {
+        SkScalar widths[kMaxStringLength];
+        SkScalar posX[kMaxStringLength];
+        SkPoint pos[kMaxStringLength];
+        int length = strlen(text);
+        SkASSERT(length <= kMaxStringLength);
+
+        paint.getTextWidths(text, length, widths);
+
+        float originX;
+        switch (paint.getTextAlign()) {
+            case SkPaint::kRight_Align: originX = 1; break;
+            case SkPaint::kCenter_Align: originX = 0.5f; break;
+            case SkPaint::kLeft_Align: originX = 0; break;
+            default: SkFAIL("Invalid paint origin"); return;
+        }
+
+        float x = kTextHeight;
+        for (int i = 0; i < length; ++i) {
+            posX[i] = x + originX * widths[i];
+            pos[i].set(posX[i], i ? pos[i - 1].y() + 3 : y + kTextHeight);
+            x += widths[i];
+        }
+
+        canvas->drawPosTextH(text, length, posX, y, paint);
+        canvas->drawPosText(text, length, pos, paint);
+    }
+
+private:
+
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* GlyphPosAlignFactory(void*) {
+    return new GlyphPosAlignGM();
+}
+
+static GMRegistry reg(GlyphPosAlignFactory);
+
+}
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index b956351..1ccccff 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -82,6 +82,7 @@
     '../gm/getpostextpath.cpp',
     '../gm/giantbitmap.cpp',
     '../gm/glyph_pos.cpp',
+    '../gm/glyph_pos_align.cpp',
     '../gm/gradients.cpp',
     '../gm/gradients_2pt_conical.cpp',
     '../gm/gradients_no_texture.cpp',
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp
index 0a09a2f..d3d711f 100644
--- a/src/gpu/GrStencilAndCoverTextContext.cpp
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp
@@ -151,24 +151,23 @@
     // will turn off the use of device-space glyphs when perspective transforms
     // are in use.
 
-    fGlyphTransform = fContext->getMatrix();
-
-    this->init(paint, skPaint, byteLength);
+    this->init(paint, skPaint, byteLength, kUseIfNeeded_DeviceSpaceGlyphsBehavior);
 
     SkMatrix* glyphCacheTransform = NULL;
     // Transform our starting point.
     if (fNeedsDeviceSpaceGlyphs) {
         SkPoint loc;
-        fGlyphTransform.mapXY(x, y, &loc);
+        fContextInitialMatrix.mapXY(x, y, &loc);
         x = loc.fX;
         y = loc.fY;
-        glyphCacheTransform = &fGlyphTransform;
+        glyphCacheTransform = &fContextInitialMatrix;
     }
 
     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
     SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform);
     fGlyphCache = autoCache.getCache();
     fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke);
+    fTransformType = GrDrawTarget::kTranslate_PathTransformType;
 
     const char* stop = text + byteLength;
 
@@ -242,9 +241,9 @@
     // glyphs. That already uses glyph cache without device transforms. Device
     // transform is not part of SkPaint::measureText API, and thus we use the
     // same glyphs as what were measured.
-    fGlyphTransform.reset();
 
-    this->init(paint, skPaint, byteLength);
+    const float textTranslateY = (1 == scalarsPerPosition ? constY : 0);
+    this->init(paint, skPaint, byteLength, kDoNotUse_DeviceSpaceGlyphsBehavior, textTranslateY);
 
     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
 
@@ -253,20 +252,32 @@
     fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke);
 
     const char* stop = text + byteLength;
-    SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
-    SkTextMapStateProc tmsProc(SkMatrix::I(), constY, scalarsPerPosition);
 
     if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
-        while (text < stop) {
-            SkPoint loc;
-            tmsProc(pos, &loc);
-            const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
-            if (glyph.fWidth) {
-                this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
+        if (1 == scalarsPerPosition) {
+            fTransformType = GrDrawTarget::kTranslateX_PathTransformType;
+            while (text < stop) {
+                const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
+                if (glyph.fWidth) {
+                    this->appendGlyph(glyph.getGlyphID(), *pos);
+                }
+                pos++;
             }
-            pos += scalarsPerPosition;
+        } else {
+            SkASSERT(2 == scalarsPerPosition);
+            fTransformType = GrDrawTarget::kTranslate_PathTransformType;
+            while (text < stop) {
+                const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
+                if (glyph.fWidth) {
+                    this->appendGlyph(glyph.getGlyphID(), pos[0], pos[1]);
+                }
+                pos += 2;
+            }
         }
     } else {
+        fTransformType = GrDrawTarget::kTranslate_PathTransformType;
+        SkTextMapStateProc tmsProc(SkMatrix::I(), 0, scalarsPerPosition);
+        SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
         while (text < stop) {
             const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
             if (glyph.fWidth) {
@@ -310,16 +321,21 @@
 
 void GrStencilAndCoverTextContext::init(const GrPaint& paint,
                                         const SkPaint& skPaint,
-                                        size_t textByteLength) {
+                                        size_t textByteLength,
+                                        DeviceSpaceGlyphsBehavior deviceSpaceGlyphsBehavior,
+                                        SkScalar textTranslateY) {
     GrTextContext::init(paint, skPaint);
 
+    fContextInitialMatrix = fContext->getMatrix();
+
     bool otherBackendsWillDrawAsPaths =
-        SkDraw::ShouldDrawTextAsPaths(skPaint, fContext->getMatrix());
+        SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
 
     if (otherBackendsWillDrawAsPaths) {
         // This is to reproduce SkDraw::drawText_asPaths glyph positions.
         fSkPaint.setLinearText(true);
         fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
+        fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
         fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
         if (fSkPaint.getStyle() != SkPaint::kFill_Style) {
             // Compensate the glyphs being scaled up by fTextRatio by scaling the
@@ -328,24 +344,35 @@
         }
         fNeedsDeviceSpaceGlyphs = false;
     } else {
-        fTextRatio = 1.0f;
-        fNeedsDeviceSpaceGlyphs = (fGlyphTransform.getType() &
-            (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0;
+        fTextRatio = fTextInverseRatio = 1.0f;
+        fNeedsDeviceSpaceGlyphs =
+            kUseIfNeeded_DeviceSpaceGlyphsBehavior == deviceSpaceGlyphsBehavior &&
+            (fContextInitialMatrix.getType() &
+                (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0;
         // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
-        SkASSERT(!fGlyphTransform.hasPerspective());
-        if (fNeedsDeviceSpaceGlyphs) {
-            fPaint.localCoordChangeInverse(fGlyphTransform);
-            fContext->setIdentityMatrix();
-        }
+        SkASSERT(!fContextInitialMatrix.hasPerspective());
     }
 
     fStroke = SkStrokeRec(fSkPaint);
 
     if (fNeedsDeviceSpaceGlyphs) {
+        SkASSERT(1.0f == fTextRatio);
+        SkASSERT(0.0f == textTranslateY);
+        fPaint.localCoordChangeInverse(fContextInitialMatrix);
+        fContext->setIdentityMatrix();
+
         // The whole shape is baked into the glyph. Make NVPR just fill the
         // baked shape.
         fStroke.setStrokeStyle(-1, false);
     } else {
+        if (1.0f != fTextRatio || 0.0f != textTranslateY) {
+            SkMatrix textMatrix;
+            textMatrix.setTranslate(0, textTranslateY);
+            textMatrix.preScale(fTextRatio, fTextRatio);
+            fPaint.localCoordChange(textMatrix);
+            fContext->concatMatrix(textMatrix);
+        }
+
         if (fSkPaint.getStrokeWidth() == 0.0f) {
             if (fSkPaint.getStyle() == SkPaint::kStrokeAndFill_Style) {
                 fStroke.setStrokeStyle(-1, false);
@@ -353,7 +380,7 @@
                 // Approximate hairline stroke.
                 const SkMatrix& ctm = fContext->getMatrix();
                 SkScalar strokeWidth = SK_Scalar1 /
-                    (fTextRatio * SkVector::Make(ctm.getScaleX(), ctm.getSkewY()).length());
+                    (SkVector::Make(ctm.getScaleX(), ctm.getSkewY()).length());
                 fStroke.setStrokeStyle(strokeWidth, false);
             }
         }
@@ -380,7 +407,9 @@
     SkASSERT(0 == fPendingGlyphCount);
 }
 
-inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
+inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x) {
+    SkASSERT(GrDrawTarget::kTranslateX_PathTransformType == fTransformType);
+
     if (fPendingGlyphCount >= kGlyphBufferSize) {
         this->flush();
     }
@@ -388,12 +417,23 @@
     fGlyphs->preloadGlyph(glyphID, fGlyphCache);
 
     fIndexBuffer[fPendingGlyphCount] = glyphID;
-    fTransformBuffer[6 * fPendingGlyphCount + 0] = fTextRatio;
-    fTransformBuffer[6 * fPendingGlyphCount + 1] = 0;
-    fTransformBuffer[6 * fPendingGlyphCount + 2] = x;
-    fTransformBuffer[6 * fPendingGlyphCount + 3] = 0;
-    fTransformBuffer[6 * fPendingGlyphCount + 4] = fTextRatio;
-    fTransformBuffer[6 * fPendingGlyphCount + 5] = y;
+    fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x;
+
+    ++fPendingGlyphCount;
+}
+
+inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
+    SkASSERT(GrDrawTarget::kTranslate_PathTransformType == fTransformType);
+
+    if (fPendingGlyphCount >= kGlyphBufferSize) {
+        this->flush();
+    }
+
+    fGlyphs->preloadGlyph(glyphID, fGlyphCache);
+
+    fIndexBuffer[fPendingGlyphCount] = glyphID;
+    fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
+    fTransformBuffer[2 * fPendingGlyphCount + 1] = fTextInverseRatio * y;
 
     ++fPendingGlyphCount;
 }
@@ -404,8 +444,7 @@
     }
 
     fDrawTarget->drawPaths(fGlyphs->pathRange(), fIndexBuffer, fPendingGlyphCount,
-                           fTransformBuffer, GrDrawTarget::kAffine_PathTransformType,
-                           SkPath::kWinding_FillType);
+                           fTransformBuffer, fTransformType, SkPath::kWinding_FillType);
 
     fPendingGlyphCount = 0;
 }
@@ -419,9 +458,7 @@
 
     fDrawTarget->drawState()->stencil()->setDisabled();
     fStateRestore.set(NULL);
-    if (fNeedsDeviceSpaceGlyphs) {
-        fContext->setMatrix(fGlyphTransform);
-    }
+    fContext->setMatrix(fContextInitialMatrix);
     GrTextContext::finish();
 }
 
diff --git a/src/gpu/GrStencilAndCoverTextContext.h b/src/gpu/GrStencilAndCoverTextContext.h
index 021a84d..08d7526 100644
--- a/src/gpu/GrStencilAndCoverTextContext.h
+++ b/src/gpu/GrStencilAndCoverTextContext.h
@@ -10,6 +10,7 @@
 
 #include "GrTextContext.h"
 #include "GrDrawState.h"
+#include "GrDrawTarget.h"
 #include "SkStrokeRec.h"
 
 class GrTextStrike;
@@ -39,21 +40,29 @@
     class GlyphPathRange;
     static const int kGlyphBufferSize = 1024;
 
-    void init(const GrPaint&, const SkPaint&, size_t textByteLength);
+    enum DeviceSpaceGlyphsBehavior {
+        kUseIfNeeded_DeviceSpaceGlyphsBehavior,
+        kDoNotUse_DeviceSpaceGlyphsBehavior,
+    };
+    void init(const GrPaint&, const SkPaint&, size_t textByteLength,
+              DeviceSpaceGlyphsBehavior, SkScalar textTranslateY = 0);
     void initGlyphs(SkGlyphCache* cache);
+    void appendGlyph(uint16_t glyphID, float x);
     void appendGlyph(uint16_t glyphID, float x, float y);
     void flush();
     void finish();
 
     GrDrawState::AutoRestoreEffects fStateRestore;
     SkScalar fTextRatio;
+    float fTextInverseRatio;
     SkStrokeRec fStroke;
     SkGlyphCache* fGlyphCache;
     GlyphPathRange* fGlyphs;
     uint32_t fIndexBuffer[kGlyphBufferSize];
-    float fTransformBuffer[6 * kGlyphBufferSize];
+    float fTransformBuffer[2 * kGlyphBufferSize];
+    GrDrawTarget::PathTransformType fTransformType;
     int fPendingGlyphCount;
-    SkMatrix fGlyphTransform;
+    SkMatrix fContextInitialMatrix;
     bool fNeedsDeviceSpaceGlyphs;
 };