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;
};