aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/text
diff options
context:
space:
mode:
authorGravatar joshualitt <joshualitt@chromium.org>2015-12-11 06:11:21 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2015-12-11 06:11:21 -0800
commite804292e805917002cc3d7baa7f967fb20d2c7cb (patch)
tree5470bacbebdec462e03929dd07d1955249b31230 /src/gpu/text
parent296779832fd6175547d991ca67c735a824cadb66 (diff)
Move all text stuff to its own folder
Diffstat (limited to 'src/gpu/text')
-rw-r--r--src/gpu/text/GrAtlasTextBlob.cpp500
-rw-r--r--src/gpu/text/GrAtlasTextBlob.h388
-rw-r--r--src/gpu/text/GrAtlasTextContext.cpp822
-rw-r--r--src/gpu/text/GrAtlasTextContext.h131
-rw-r--r--src/gpu/text/GrBatchFontCache.cpp236
-rw-r--r--src/gpu/text/GrBatchFontCache.h228
-rw-r--r--src/gpu/text/GrDistanceFieldAdjustTable.cpp93
-rw-r--r--src/gpu/text/GrDistanceFieldAdjustTable.h31
-rw-r--r--src/gpu/text/GrFontScaler.cpp207
-rw-r--r--src/gpu/text/GrFontScaler.h69
-rw-r--r--src/gpu/text/GrStencilAndCoverTextContext.cpp616
-rw-r--r--src/gpu/text/GrStencilAndCoverTextContext.h145
-rw-r--r--src/gpu/text/GrTextBlobCache.cpp58
-rw-r--r--src/gpu/text/GrTextBlobCache.h158
-rw-r--r--src/gpu/text/GrTextContext.cpp176
-rw-r--r--src/gpu/text/GrTextContext.h76
-rw-r--r--src/gpu/text/GrTextUtils.cpp200
-rw-r--r--src/gpu/text/GrTextUtils.h70
18 files changed, 4204 insertions, 0 deletions
diff --git a/src/gpu/text/GrAtlasTextBlob.cpp b/src/gpu/text/GrAtlasTextBlob.cpp
new file mode 100644
index 0000000000..dee6d133e3
--- /dev/null
+++ b/src/gpu/text/GrAtlasTextBlob.cpp
@@ -0,0 +1,500 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrAtlasTextBlob.h"
+
+#include "GrBlurUtils.h"
+#include "GrContext.h"
+#include "GrDrawContext.h"
+#include "GrTextUtils.h"
+#include "SkColorFilter.h"
+#include "SkDrawFilter.h"
+#include "SkTextBlobRunIterator.h"
+#include "batches/GrAtlasTextBatch.h"
+
+void GrAtlasTextBlob::appendGlyph(int runIndex,
+ const SkRect& positions,
+ GrColor color,
+ GrBatchTextStrike* strike,
+ GrGlyph* glyph,
+ GrFontScaler* scaler, const SkGlyph& skGlyph,
+ SkScalar x, SkScalar y, SkScalar scale, bool applyVM) {
+
+ // If the glyph is too large we fall back to paths
+ if (glyph->fTooLargeForAtlas) {
+ this->appendLargeGlyph(glyph, scaler, skGlyph, x, y, scale, applyVM);
+ return;
+ }
+
+ Run& run = fRuns[runIndex];
+ GrMaskFormat format = glyph->fMaskFormat;
+
+ Run::SubRunInfo* subRun = &run.fSubRunInfo.back();
+ if (run.fInitialized && subRun->maskFormat() != format) {
+ subRun = &run.push_back();
+ subRun->setStrike(strike);
+ } else if (!run.fInitialized) {
+ subRun->setStrike(strike);
+ }
+
+ run.fInitialized = true;
+
+ size_t vertexStride = GetVertexStride(format);
+
+ subRun->setMaskFormat(format);
+
+ run.fVertexBounds.joinNonEmptyArg(positions);
+ subRun->setColor(color);
+
+ intptr_t vertex = reinterpret_cast<intptr_t>(this->fVertices + subRun->vertexEndIndex());
+
+ if (kARGB_GrMaskFormat != glyph->fMaskFormat) {
+ // V0
+ SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fLeft, positions.fTop);
+ SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+ *colorPtr = color;
+ vertex += vertexStride;
+
+ // V1
+ position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fLeft, positions.fBottom);
+ colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+ *colorPtr = color;
+ vertex += vertexStride;
+
+ // V2
+ position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fRight, positions.fBottom);
+ colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+ *colorPtr = color;
+ vertex += vertexStride;
+
+ // V3
+ position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fRight, positions.fTop);
+ colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+ *colorPtr = color;
+ } else {
+ // V0
+ SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fLeft, positions.fTop);
+ vertex += vertexStride;
+
+ // V1
+ position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fLeft, positions.fBottom);
+ vertex += vertexStride;
+
+ // V2
+ position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fRight, positions.fBottom);
+ vertex += vertexStride;
+
+ // V3
+ position = reinterpret_cast<SkPoint*>(vertex);
+ position->set(positions.fRight, positions.fTop);
+ }
+ subRun->appendVertices(vertexStride);
+ fGlyphs[subRun->glyphEndIndex()] = glyph;
+ subRun->glyphAppended();
+}
+
+void GrAtlasTextBlob::appendLargeGlyph(GrGlyph* glyph, GrFontScaler* scaler, const SkGlyph& skGlyph,
+ SkScalar x, SkScalar y, SkScalar scale, bool applyVM) {
+ if (nullptr == glyph->fPath) {
+ const SkPath* glyphPath = scaler->getGlyphPath(skGlyph);
+ if (!glyphPath) {
+ return;
+ }
+
+ glyph->fPath = new SkPath(*glyphPath);
+ }
+ fBigGlyphs.push_back(GrAtlasTextBlob::BigGlyph(*glyph->fPath, x, y, scale, applyVM));
+}
+
+bool GrAtlasTextBlob::mustRegenerate(SkScalar* outTransX, SkScalar* outTransY,
+ const SkPaint& paint,
+ GrColor color, const SkMaskFilter::BlurRec& blurRec,
+ const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
+ // If we have LCD text then our canonical color will be set to transparent, in this case we have
+ // to regenerate the blob on any color change
+ // We use the grPaint to get any color filter effects
+ if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
+ fPaintColor != color) {
+ return true;
+ }
+
+ if (fViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) {
+ return true;
+ }
+
+ if (fViewMatrix.hasPerspective() && !fViewMatrix.cheapEqualTo(viewMatrix)) {
+ return true;
+ }
+
+ // We only cache one masked version
+ if (fKey.fHasBlur &&
+ (fBlurRec.fSigma != blurRec.fSigma ||
+ fBlurRec.fStyle != blurRec.fStyle ||
+ fBlurRec.fQuality != blurRec.fQuality)) {
+ return true;
+ }
+
+ // Similarly, we only cache one version for each style
+ if (fKey.fStyle != SkPaint::kFill_Style &&
+ (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() ||
+ fStrokeInfo.fMiterLimit != paint.getStrokeMiter() ||
+ fStrokeInfo.fJoin != paint.getStrokeJoin())) {
+ return true;
+ }
+
+ // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls
+ // for mixed blobs if this becomes an issue.
+ if (this->hasBitmap() && this->hasDistanceField()) {
+ // Identical viewmatrices and we can reuse in all cases
+ if (fViewMatrix.cheapEqualTo(viewMatrix) && x == fX && y == fY) {
+ return false;
+ }
+ return true;
+ }
+
+ if (this->hasBitmap()) {
+ if (fViewMatrix.getScaleX() != viewMatrix.getScaleX() ||
+ fViewMatrix.getScaleY() != viewMatrix.getScaleY() ||
+ fViewMatrix.getSkewX() != viewMatrix.getSkewX() ||
+ fViewMatrix.getSkewY() != viewMatrix.getSkewY()) {
+ return true;
+ }
+
+ // We can update the positions in the cachedtextblobs without regenerating the whole blob,
+ // but only for integer translations.
+ // This cool bit of math will determine the necessary translation to apply to the already
+ // generated vertex coordinates to move them to the correct position
+ SkScalar transX = viewMatrix.getTranslateX() +
+ viewMatrix.getScaleX() * (x - fX) +
+ viewMatrix.getSkewX() * (y - fY) -
+ fViewMatrix.getTranslateX();
+ SkScalar transY = viewMatrix.getTranslateY() +
+ viewMatrix.getSkewY() * (x - fX) +
+ viewMatrix.getScaleY() * (y - fY) -
+ fViewMatrix.getTranslateY();
+ if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY) ) {
+ return true;
+ }
+
+ (*outTransX) = transX;
+ (*outTransY) = transY;
+ } else if (this->hasDistanceField()) {
+ // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different
+ // distance field being generated, so we have to regenerate in those cases
+ SkScalar newMaxScale = viewMatrix.getMaxScale();
+ SkScalar oldMaxScale = fViewMatrix.getMaxScale();
+ SkScalar scaleAdjust = newMaxScale / oldMaxScale;
+ if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) {
+ return true;
+ }
+
+ (*outTransX) = x - fX;
+ (*outTransY) = y - fY;
+ }
+
+
+ // If we can reuse the blob, then make sure we update the blob's viewmatrix, and x/y
+ // offsets. Note, we offset the vertex bounds right before flushing
+ fViewMatrix = viewMatrix;
+ fX = x;
+ fY = y;
+
+ // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case
+ // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate
+ // the blob anyways at flush time, so no need to regenerate explicitly
+ return false;
+}
+
+GrDrawBatch* GrAtlasTextBlob::createBatch(const Run::SubRunInfo& info,
+ int glyphCount, int run, int subRun,
+ GrColor color, SkScalar transX, SkScalar transY,
+ const SkPaint& skPaint, const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ GrBatchFontCache* cache) {
+ GrMaskFormat format = info.maskFormat();
+ GrColor subRunColor;
+ if (kARGB_GrMaskFormat == format) {
+ uint8_t paintAlpha = skPaint.getAlpha();
+ subRunColor = SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha);
+ } else {
+ subRunColor = color;
+ }
+
+ GrAtlasTextBatch* batch;
+ if (info.drawAsDistanceFields()) {
+ SkColor filteredColor;
+ SkColorFilter* colorFilter = skPaint.getColorFilter();
+ if (colorFilter) {
+ filteredColor = colorFilter->filterColor(skPaint.getColor());
+ } else {
+ filteredColor = skPaint.getColor();
+ }
+ bool useBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
+ batch = GrAtlasTextBatch::CreateDistanceField(glyphCount, cache,
+ distanceAdjustTable, filteredColor,
+ info.hasUseLCDText(), useBGR);
+ } else {
+ batch = GrAtlasTextBatch::CreateBitmap(format, glyphCount, cache);
+ }
+ GrAtlasTextBatch::Geometry& geometry = batch->geometry();
+ geometry.fBlob = SkRef(this);
+ geometry.fRun = run;
+ geometry.fSubRun = subRun;
+ geometry.fColor = subRunColor;
+ geometry.fTransX = transX;
+ geometry.fTransY = transY;
+ batch->init();
+
+ return batch;
+}
+
+inline
+void GrAtlasTextBlob::flushRun(GrDrawContext* dc, GrPipelineBuilder* pipelineBuilder,
+ int run, GrColor color,
+ SkScalar transX, SkScalar transY,
+ const SkPaint& skPaint, const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ GrBatchFontCache* cache) {
+ for (int subRun = 0; subRun < fRuns[run].fSubRunInfo.count(); subRun++) {
+ const Run::SubRunInfo& info = fRuns[run].fSubRunInfo[subRun];
+ int glyphCount = info.glyphCount();
+ if (0 == glyphCount) {
+ continue;
+ }
+
+ SkAutoTUnref<GrDrawBatch> batch(this->createBatch(info, glyphCount, run,
+ subRun, color, transX, transY,
+ skPaint, props,
+ distanceAdjustTable, cache));
+ dc->drawBatch(pipelineBuilder, batch);
+ }
+}
+
+void GrAtlasTextBlob::flushBigGlyphs(GrContext* context, GrDrawContext* dc,
+ const GrClip& clip, const SkPaint& skPaint,
+ SkScalar transX, SkScalar transY,
+ const SkIRect& clipBounds) {
+ for (int i = 0; i < fBigGlyphs.count(); i++) {
+ GrAtlasTextBlob::BigGlyph& bigGlyph = fBigGlyphs[i];
+ bigGlyph.fVx += transX;
+ bigGlyph.fVy += transY;
+ SkMatrix ctm;
+ ctm.setScale(bigGlyph.fScale, bigGlyph.fScale);
+ ctm.postTranslate(bigGlyph.fVx, bigGlyph.fVy);
+ if (bigGlyph.fApplyVM) {
+ ctm.postConcat(fViewMatrix);
+ }
+
+ GrBlurUtils::drawPathWithMaskFilter(context, dc, clip, bigGlyph.fPath,
+ skPaint, ctm, nullptr, clipBounds, false);
+ }
+}
+
+void GrAtlasTextBlob::flushRunAsPaths(GrContext* context, GrDrawContext* dc,
+ const SkSurfaceProps& props,
+ const SkTextBlobRunIterator& it,
+ const GrClip& clip, const SkPaint& skPaint,
+ SkDrawFilter* drawFilter, const SkMatrix& viewMatrix,
+ const SkIRect& clipBounds, SkScalar x, SkScalar y) {
+ SkPaint runPaint = skPaint;
+
+ size_t textLen = it.glyphCount() * sizeof(uint16_t);
+ const SkPoint& offset = it.offset();
+
+ it.applyFontToPaint(&runPaint);
+
+ if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
+ return;
+ }
+
+ runPaint.setFlags(GrTextContext::FilterTextFlags(props, runPaint));
+
+ switch (it.positioning()) {
+ case SkTextBlob::kDefault_Positioning:
+ GrTextUtils::DrawTextAsPath(context, dc, clip, runPaint, viewMatrix,
+ (const char *)it.glyphs(),
+ textLen, x + offset.x(), y + offset.y(), clipBounds);
+ break;
+ case SkTextBlob::kHorizontal_Positioning:
+ GrTextUtils::DrawPosTextAsPath(context, dc, props, clip, runPaint, viewMatrix,
+ (const char*)it.glyphs(),
+ textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()),
+ clipBounds);
+ break;
+ case SkTextBlob::kFull_Positioning:
+ GrTextUtils::DrawPosTextAsPath(context, dc, props, clip, runPaint, viewMatrix,
+ (const char*)it.glyphs(),
+ textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds);
+ break;
+ }
+}
+
+void GrAtlasTextBlob::flushCached(GrContext* context,
+ GrDrawContext* dc,
+ const SkTextBlob* blob,
+ const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ const SkPaint& skPaint,
+ const GrPaint& grPaint,
+ SkDrawFilter* drawFilter,
+ const GrClip& clip,
+ const SkMatrix& viewMatrix,
+ const SkIRect& clipBounds,
+ SkScalar x, SkScalar y,
+ SkScalar transX, SkScalar transY) {
+ // We loop through the runs of the blob, flushing each. If any run is too large, then we flush
+ // it as paths
+ GrPipelineBuilder pipelineBuilder(grPaint, dc->accessRenderTarget(), clip);
+
+ GrColor color = grPaint.getColor();
+
+ SkTextBlobRunIterator it(blob);
+ for (int run = 0; !it.done(); it.next(), run++) {
+ if (fRuns[run].fDrawAsPaths) {
+ this->flushRunAsPaths(context, dc, props, it, clip, skPaint,
+ drawFilter, viewMatrix, clipBounds, x, y);
+ continue;
+ }
+ fRuns[run].fVertexBounds.offset(transX, transY);
+ this->flushRun(dc, &pipelineBuilder, run, color,
+ transX, transY, skPaint, props,
+ distanceAdjustTable, context->getBatchFontCache());
+ }
+
+ // Now flush big glyphs
+ this->flushBigGlyphs(context, dc, clip, skPaint, transX, transY, clipBounds);
+}
+
+void GrAtlasTextBlob::flushThrowaway(GrContext* context,
+ GrDrawContext* dc,
+ const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ const SkPaint& skPaint,
+ const GrPaint& grPaint,
+ const GrClip& clip,
+ const SkIRect& clipBounds) {
+ GrPipelineBuilder pipelineBuilder(grPaint, dc->accessRenderTarget(), clip);
+
+ GrColor color = grPaint.getColor();
+ for (int run = 0; run < fRunCount; run++) {
+ this->flushRun(dc, &pipelineBuilder, run, color, 0, 0, skPaint, props,
+ distanceAdjustTable, context->getBatchFontCache());
+ }
+
+ // Now flush big glyphs
+ this->flushBigGlyphs(context, dc, clip, skPaint, 0, 0, clipBounds);
+}
+
+
+// TODO get this code building again
+#ifdef CACHE_SANITY_CHECK
+void GrAtlasTextBlob::AssertEqual(const GrAtlasTextBlob& l, const GrAtlasTextBlob& r) {
+ SkASSERT(l.fSize == r.fSize);
+ SkASSERT(l.fPool == r.fPool);
+
+ SkASSERT(l.fBlurRec.fSigma == r.fBlurRec.fSigma);
+ SkASSERT(l.fBlurRec.fStyle == r.fBlurRec.fStyle);
+ SkASSERT(l.fBlurRec.fQuality == r.fBlurRec.fQuality);
+
+ SkASSERT(l.fStrokeInfo.fFrameWidth == r.fStrokeInfo.fFrameWidth);
+ SkASSERT(l.fStrokeInfo.fMiterLimit == r.fStrokeInfo.fMiterLimit);
+ SkASSERT(l.fStrokeInfo.fJoin == r.fStrokeInfo.fJoin);
+
+ SkASSERT(l.fBigGlyphs.count() == r.fBigGlyphs.count());
+ for (int i = 0; i < l.fBigGlyphs.count(); i++) {
+ const BigGlyph& lBigGlyph = l.fBigGlyphs[i];
+ const BigGlyph& rBigGlyph = r.fBigGlyphs[i];
+
+ SkASSERT(lBigGlyph.fPath == rBigGlyph.fPath);
+ // We can't assert that these have the same translations
+ }
+
+ SkASSERT(l.fKey == r.fKey);
+ SkASSERT(l.fViewMatrix.cheapEqualTo(r.fViewMatrix));
+ SkASSERT(l.fPaintColor == r.fPaintColor);
+ SkASSERT(l.fMaxMinScale == r.fMaxMinScale);
+ SkASSERT(l.fMinMaxScale == r.fMinMaxScale);
+ SkASSERT(l.fTextType == r.fTextType);
+
+ SkASSERT(l.fRunCount == r.fRunCount);
+ for (int i = 0; i < l.fRunCount; i++) {
+ const Run& lRun = l.fRuns[i];
+ const Run& rRun = r.fRuns[i];
+
+ if (lRun.fStrike.get()) {
+ SkASSERT(rRun.fStrike.get());
+ SkASSERT(GrBatchTextStrike::GetKey(*lRun.fStrike) ==
+ GrBatchTextStrike::GetKey(*rRun.fStrike));
+
+ } else {
+ SkASSERT(!rRun.fStrike.get());
+ }
+
+ if (lRun.fTypeface.get()) {
+ SkASSERT(rRun.fTypeface.get());
+ SkASSERT(SkTypeface::Equal(lRun.fTypeface, rRun.fTypeface));
+ } else {
+ SkASSERT(!rRun.fTypeface.get());
+ }
+
+ // We offset bounds right before flush time so they will not be correct here
+ //SkASSERT(lRun.fVertexBounds == rRun.fVertexBounds);
+
+ SkASSERT(lRun.fDescriptor.getDesc());
+ SkASSERT(rRun.fDescriptor.getDesc());
+ SkASSERT(lRun.fDescriptor.getDesc()->equals(*rRun.fDescriptor.getDesc()));
+
+ if (lRun.fOverrideDescriptor.get()) {
+ SkASSERT(lRun.fOverrideDescriptor->getDesc());
+ SkASSERT(rRun.fOverrideDescriptor.get() && rRun.fOverrideDescriptor->getDesc());;
+ SkASSERT(lRun.fOverrideDescriptor->getDesc()->equals(
+ *rRun.fOverrideDescriptor->getDesc()));
+ } else {
+ SkASSERT(!rRun.fOverrideDescriptor.get());
+ }
+
+ // color can be changed
+ //SkASSERT(lRun.fColor == rRun.fColor);
+ SkASSERT(lRun.fInitialized == rRun.fInitialized);
+ SkASSERT(lRun.fDrawAsPaths == rRun.fDrawAsPaths);
+
+ SkASSERT(lRun.fSubRunInfo.count() == rRun.fSubRunInfo.count());
+ for(int j = 0; j < lRun.fSubRunInfo.count(); j++) {
+ const Run::SubRunInfo& lSubRun = lRun.fSubRunInfo[j];
+ const Run::SubRunInfo& rSubRun = rRun.fSubRunInfo[j];
+
+ SkASSERT(lSubRun.fVertexStartIndex == rSubRun.fVertexStartIndex);
+ SkASSERT(lSubRun.fVertexEndIndex == rSubRun.fVertexEndIndex);
+ SkASSERT(lSubRun.fGlyphStartIndex == rSubRun.fGlyphStartIndex);
+ SkASSERT(lSubRun.fGlyphEndIndex == rSubRun.fGlyphEndIndex);
+ SkASSERT(lSubRun.fTextRatio == rSubRun.fTextRatio);
+ SkASSERT(lSubRun.fMaskFormat == rSubRun.fMaskFormat);
+ SkASSERT(lSubRun.fDrawAsDistanceFields == rSubRun.fDrawAsDistanceFields);
+ SkASSERT(lSubRun.fUseLCDText == rSubRun.fUseLCDText);
+
+ //We can't compare the bulk use tokens with this method
+ /*
+ SkASSERT(lSubRun.fBulkUseToken.fPlotsToUpdate.count() ==
+ rSubRun.fBulkUseToken.fPlotsToUpdate.count());
+ SkASSERT(lSubRun.fBulkUseToken.fPlotAlreadyUpdated ==
+ rSubRun.fBulkUseToken.fPlotAlreadyUpdated);
+ for (int k = 0; k < lSubRun.fBulkUseToken.fPlotsToUpdate.count(); k++) {
+ SkASSERT(lSubRun.fBulkUseToken.fPlotsToUpdate[k] ==
+ rSubRun.fBulkUseToken.fPlotsToUpdate[k]);
+ }*/
+ }
+ }
+}
+
+#endif
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
new file mode 100644
index 0000000000..d99ae707e0
--- /dev/null
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrAtlasTextBlob_DEFINED
+#define GrAtlasTextBlob_DEFINED
+
+#include "GrBatchAtlas.h"
+#include "GrBatchFontCache.h"
+#include "GrColor.h"
+#include "GrMemoryPool.h"
+#include "SkDescriptor.h"
+#include "SkMaskFilter.h"
+#include "SkSurfaceProps.h"
+#include "SkTInternalLList.h"
+
+struct GrDistanceFieldAdjustTable;
+class GrTextContext;
+class SkDrawFilter;
+class SkTextBlob;
+class SkTextBlobRunIterator;
+
+// With this flag enabled, the GrAtlasTextContext will, as a sanity check, regenerate every blob
+// that comes in to verify the integrity of its cache
+//#define CACHE_SANITY_CHECK // VERY SLOW
+
+/*
+ * A GrAtlasTextBlob contains a fully processed SkTextBlob, suitable for nearly immediate drawing
+ * on the GPU. These are initially created with valid positions and colors, but invalid
+ * texture coordinates. The GrAtlasTextBlob itself has a few Blob-wide properties, and also
+ * consists of a number of runs. Runs inside a blob are flushed individually so they can be
+ * reordered.
+ *
+ * The only thing(aside from a memcopy) required to flush a GrAtlasTextBlob is to ensure that
+ * the GrAtlas will not evict anything the Blob needs.
+ *
+ * Note: This struct should really be named GrCachedAtasTextBlob, but that is too verbose.
+ *
+ * *WARNING* If you add new fields to this struct, then you may need to to update AssertEqual
+ */
+class GrAtlasTextBlob : public SkNVRefCnt<GrAtlasTextBlob> {
+public:
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrAtlasTextBlob);
+
+ /*
+ * Each Run inside of the blob can have its texture coordinates regenerated if required.
+ * To determine if regeneration is necessary, fAtlasGeneration is used. If there have been
+ * any evictions inside of the atlas, then we will simply regenerate Runs. We could track
+ * this at a more fine grained level, but its not clear if this is worth it, as evictions
+ * should be fairly rare.
+ *
+ * One additional point, each run can contain glyphs with any of the three mask formats.
+ * We call these SubRuns. Because a subrun must be a contiguous range, we have to create
+ * a new subrun each time the mask format changes in a run. In theory, a run can have as
+ * many SubRuns as it has glyphs, ie if a run alternates between color emoji and A8. In
+ * practice, the vast majority of runs have only a single subrun.
+ *
+ * Finally, for runs where the entire thing is too large for the GrAtlasTextContext to
+ * handle, we have a bit to mark the run as flusahable via rendering as paths. It is worth
+ * pointing. It would be a bit expensive to figure out ahead of time whether or not a run
+ * can flush in this manner, so we always allocate vertices for the run, regardless of
+ * whether or not it is too large. The benefit of this strategy is that we can always reuse
+ * a blob allocation regardless of viewmatrix changes. We could store positions for these
+ * glyphs. However, its not clear if this is a win because we'd still have to either go the
+ * glyph cache to get the path at flush time, or hold onto the path in the cache, which
+ * would greatly increase the memory of these cached items.
+ */
+ struct Run {
+ Run()
+ : fInitialized(false)
+ , fDrawAsPaths(false) {
+ fVertexBounds.setLargestInverted();
+ // To ensure we always have one subrun, we push back a fresh run here
+ fSubRunInfo.push_back();
+ }
+ struct SubRunInfo {
+ SubRunInfo()
+ : fAtlasGeneration(GrBatchAtlas::kInvalidAtlasGeneration)
+ , fVertexStartIndex(0)
+ , fVertexEndIndex(0)
+ , fGlyphStartIndex(0)
+ , fGlyphEndIndex(0)
+ , fColor(GrColor_ILLEGAL)
+ , fMaskFormat(kA8_GrMaskFormat)
+ , fDrawAsDistanceFields(false)
+ , fUseLCDText(false) {}
+ SubRunInfo(const SubRunInfo& that)
+ : fBulkUseToken(that.fBulkUseToken)
+ , fStrike(SkSafeRef(that.fStrike.get()))
+ , fAtlasGeneration(that.fAtlasGeneration)
+ , fVertexStartIndex(that.fVertexStartIndex)
+ , fVertexEndIndex(that.fVertexEndIndex)
+ , fGlyphStartIndex(that.fGlyphStartIndex)
+ , fGlyphEndIndex(that.fGlyphEndIndex)
+ , fColor(that.fColor)
+ , fMaskFormat(that.fMaskFormat)
+ , fDrawAsDistanceFields(that.fDrawAsDistanceFields)
+ , fUseLCDText(that.fUseLCDText) {
+ }
+
+ // TODO when this object is more internal, drop the privacy
+ void resetBulkUseToken() { fBulkUseToken.reset(); }
+ GrBatchAtlas::BulkUseTokenUpdater* bulkUseToken() { return &fBulkUseToken; }
+ void setStrike(GrBatchTextStrike* strike) { fStrike.reset(SkRef(strike)); }
+ GrBatchTextStrike* strike() const { return fStrike.get(); }
+
+ void setAtlasGeneration(uint64_t atlasGeneration) { fAtlasGeneration = atlasGeneration;}
+ uint64_t atlasGeneration() const { return fAtlasGeneration; }
+
+ size_t byteCount() const { return fVertexEndIndex - fVertexStartIndex; }
+ size_t vertexStartIndex() const { return fVertexStartIndex; }
+ size_t vertexEndIndex() const { return fVertexEndIndex; }
+ void appendVertices(size_t vertexStride) {
+ fVertexEndIndex += vertexStride * kVerticesPerGlyph;
+ }
+
+ uint32_t glyphCount() const { return fGlyphEndIndex - fGlyphStartIndex; }
+ uint32_t glyphStartIndex() const { return fGlyphStartIndex; }
+ uint32_t glyphEndIndex() const { return fGlyphEndIndex; }
+ void glyphAppended() { fGlyphEndIndex++; }
+ void setColor(GrColor color) { fColor = color; }
+ GrColor color() const { return fColor; }
+ void setMaskFormat(GrMaskFormat format) { fMaskFormat = format; }
+ GrMaskFormat maskFormat() const { return fMaskFormat; }
+
+ void setAsSuccessor(const SubRunInfo& prev) {
+ fGlyphStartIndex = prev.glyphEndIndex();
+ fGlyphEndIndex = prev.glyphEndIndex();
+
+ fVertexStartIndex = prev.vertexEndIndex();
+ fVertexEndIndex = prev.vertexEndIndex();
+ }
+
+ // df properties
+ void setUseLCDText(bool useLCDText) { fUseLCDText = useLCDText; }
+ bool hasUseLCDText() const { return fUseLCDText; }
+ void setDrawAsDistanceFields() { fDrawAsDistanceFields = true; }
+ bool drawAsDistanceFields() const { return fDrawAsDistanceFields; }
+
+ private:
+ GrBatchAtlas::BulkUseTokenUpdater fBulkUseToken;
+ SkAutoTUnref<GrBatchTextStrike> fStrike;
+ uint64_t fAtlasGeneration;
+ size_t fVertexStartIndex;
+ size_t fVertexEndIndex;
+ uint32_t fGlyphStartIndex;
+ uint32_t fGlyphEndIndex;
+ GrColor fColor;
+ GrMaskFormat fMaskFormat;
+ bool fDrawAsDistanceFields; // df property
+ bool fUseLCDText; // df property
+ };
+
+ SubRunInfo& push_back() {
+ // Forward glyph / vertex information to seed the new sub run
+ SubRunInfo& newSubRun = fSubRunInfo.push_back();
+ const SubRunInfo& prevSubRun = fSubRunInfo.fromBack(1);
+
+ newSubRun.setAsSuccessor(prevSubRun);
+ return newSubRun;
+ }
+ static const int kMinSubRuns = 1;
+ SkAutoTUnref<SkTypeface> fTypeface;
+ SkRect fVertexBounds;
+ SkSTArray<kMinSubRuns, SubRunInfo> fSubRunInfo;
+ SkAutoDescriptor fDescriptor;
+
+ // Distance field text cannot draw coloremoji, and so has to fall back. However,
+ // though the distance field text and the coloremoji may share the same run, they
+ // will have different descriptors. If fOverrideDescriptor is non-nullptr, then it
+ // will be used in place of the run's descriptor to regen texture coords
+ SkAutoTDelete<SkAutoDescriptor> fOverrideDescriptor; // df properties
+ bool fInitialized;
+ bool fDrawAsPaths;
+ };
+
+ struct BigGlyph {
+ BigGlyph(const SkPath& path, SkScalar vx, SkScalar vy, SkScalar scale, bool applyVM)
+ : fPath(path)
+ , fVx(vx)
+ , fVy(vy)
+ , fScale(scale)
+ , fApplyVM(applyVM) {}
+ SkPath fPath;
+ SkScalar fVx;
+ SkScalar fVy;
+ SkScalar fScale;
+ bool fApplyVM;
+ };
+
+ struct Key {
+ Key() {
+ sk_bzero(this, sizeof(Key));
+ }
+ uint32_t fUniqueID;
+ // Color may affect the gamma of the mask we generate, but in a fairly limited way.
+ // Each color is assigned to on of a fixed number of buckets based on its
+ // luminance. For each luminance bucket there is a "canonical color" that
+ // represents the bucket. This functionality is currently only supported for A8
+ SkColor fCanonicalColor;
+ SkPaint::Style fStyle;
+ SkPixelGeometry fPixelGeometry;
+ bool fHasBlur;
+
+ bool operator==(const Key& other) const {
+ return 0 == memcmp(this, &other, sizeof(Key));
+ }
+ };
+
+ struct StrokeInfo {
+ SkScalar fFrameWidth;
+ SkScalar fMiterLimit;
+ SkPaint::Join fJoin;
+ };
+
+ enum TextType {
+ kHasDistanceField_TextType = 0x1,
+ kHasBitmap_TextType = 0x2,
+ };
+
+ // all glyph / vertex offsets are into these pools.
+ unsigned char* fVertices;
+ GrGlyph** fGlyphs;
+ Run* fRuns;
+ GrMemoryPool* fPool;
+ SkMaskFilter::BlurRec fBlurRec;
+ StrokeInfo fStrokeInfo;
+ SkTArray<BigGlyph> fBigGlyphs;
+ Key fKey;
+ SkMatrix fViewMatrix;
+ GrColor fPaintColor;
+ SkScalar fX;
+ SkScalar fY;
+
+ // We can reuse distance field text, but only if the new viewmatrix would not result in
+ // a mip change. Because there can be multiple runs in a blob, we track the overall
+ // maximum minimum scale, and minimum maximum scale, we can support before we need to regen
+ SkScalar fMaxMinScale;
+ SkScalar fMinMaxScale;
+ int fRunCount;
+ uint8_t fTextType;
+
+ GrAtlasTextBlob()
+ : fMaxMinScale(-SK_ScalarMax)
+ , fMinMaxScale(SK_ScalarMax)
+ , fTextType(0) {}
+
+ ~GrAtlasTextBlob() {
+ for (int i = 0; i < fRunCount; i++) {
+ fRuns[i].~Run();
+ }
+ }
+
+ static const Key& GetKey(const GrAtlasTextBlob& blob) {
+ return blob.fKey;
+ }
+
+ static uint32_t Hash(const Key& key) {
+ return SkChecksum::Murmur3(&key, sizeof(Key));
+ }
+
+ void operator delete(void* p) {
+ GrAtlasTextBlob* blob = reinterpret_cast<GrAtlasTextBlob*>(p);
+ blob->fPool->release(p);
+ }
+ void* operator new(size_t) {
+ SkFAIL("All blobs are created by placement new.");
+ return sk_malloc_throw(0);
+ }
+
+ void* operator new(size_t, void* p) { return p; }
+ void operator delete(void* target, void* placement) {
+ ::operator delete(target, placement);
+ }
+
+ bool hasDistanceField() const { return SkToBool(fTextType & kHasDistanceField_TextType); }
+ bool hasBitmap() const { return SkToBool(fTextType & kHasBitmap_TextType); }
+ void setHasDistanceField() { fTextType |= kHasDistanceField_TextType; }
+ void setHasBitmap() { fTextType |= kHasBitmap_TextType; }
+
+ void push_back_run(int currRun) {
+ SkASSERT(currRun < fRunCount);
+ if (currRun > 0) {
+ Run::SubRunInfo& newRun = fRuns[currRun].fSubRunInfo.back();
+ Run::SubRunInfo& lastRun = fRuns[currRun - 1].fSubRunInfo.back();
+ newRun.setAsSuccessor(lastRun);
+ }
+ }
+
+ // Appends a glyph to the blob. If the glyph is too large, the glyph will be appended
+ // as a path.
+ void appendGlyph(int runIndex,
+ const SkRect& positions,
+ GrColor color,
+ GrBatchTextStrike* strike,
+ GrGlyph* glyph,
+ GrFontScaler* scaler, const SkGlyph& skGlyph,
+ SkScalar x, SkScalar y, SkScalar scale, bool applyVM);
+
+ static size_t GetVertexStride(GrMaskFormat maskFormat) {
+ switch (maskFormat) {
+ case kA8_GrMaskFormat:
+ return kGrayTextVASize;
+ case kARGB_GrMaskFormat:
+ return kColorTextVASize;
+ default:
+ return kLCDTextVASize;
+ }
+ }
+
+ bool mustRegenerate(SkScalar* outTransX, SkScalar* outTransY, const SkPaint& paint,
+ GrColor color, const SkMaskFilter::BlurRec& blurRec,
+ const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
+
+ // flush a GrAtlasTextBlob associated with a SkTextBlob
+ void flushCached(GrContext* context,
+ GrDrawContext* dc,
+ const SkTextBlob* blob,
+ const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ const SkPaint& skPaint,
+ const GrPaint& grPaint,
+ SkDrawFilter* drawFilter,
+ const GrClip& clip,
+ const SkMatrix& viewMatrix,
+ const SkIRect& clipBounds,
+ SkScalar x, SkScalar y,
+ SkScalar transX, SkScalar transY);
+
+ // flush a throwaway GrAtlasTextBlob *not* associated with an SkTextBlob
+ void flushThrowaway(GrContext* context,
+ GrDrawContext* dc,
+ const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ const SkPaint& skPaint,
+ const GrPaint& grPaint,
+ const GrClip& clip,
+ const SkIRect& clipBounds);
+
+ // position + local coord
+ static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
+ static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16);
+ static const size_t kLCDTextVASize = kGrayTextVASize;
+ static const int kVerticesPerGlyph = 4;
+
+#ifdef CACHE_SANITY_CHECK
+ static void AssertEqual(const GrAtlasTextBlob&, const GrAtlasTextBlob&);
+ size_t fSize;
+#endif
+
+ // We'd like to inline this and make it private, but there is some test code which calls it.
+ // TODO refactor this
+ GrDrawBatch* createBatch(const Run::SubRunInfo& info,
+ int glyphCount, int run, int subRun,
+ GrColor color, SkScalar transX, SkScalar transY,
+ const SkPaint& skPaint, const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ GrBatchFontCache* cache);
+
+private:
+ void appendLargeGlyph(GrGlyph* glyph, GrFontScaler* scaler, const SkGlyph& skGlyph,
+ SkScalar x, SkScalar y, SkScalar scale, bool applyVM);
+
+ inline void flushRun(GrDrawContext* dc, GrPipelineBuilder* pipelineBuilder,
+ int run, GrColor color,
+ SkScalar transX, SkScalar transY,
+ const SkPaint& skPaint, const SkSurfaceProps& props,
+ const GrDistanceFieldAdjustTable* distanceAdjustTable,
+ GrBatchFontCache* cache);
+
+ void flushBigGlyphs(GrContext* context, GrDrawContext* dc,
+ const GrClip& clip, const SkPaint& skPaint,
+ SkScalar transX, SkScalar transY,
+ const SkIRect& clipBounds);
+
+ void flushRunAsPaths(GrContext* context,
+ GrDrawContext* dc,
+ const SkSurfaceProps& props,
+ const SkTextBlobRunIterator& it,
+ const GrClip& clip, const SkPaint& skPaint,
+ SkDrawFilter* drawFilter, const SkMatrix& viewMatrix,
+ const SkIRect& clipBounds, SkScalar x, SkScalar y);
+};
+
+#endif
diff --git a/src/gpu/text/GrAtlasTextContext.cpp b/src/gpu/text/GrAtlasTextContext.cpp
new file mode 100644
index 0000000000..aca839cf2c
--- /dev/null
+++ b/src/gpu/text/GrAtlasTextContext.cpp
@@ -0,0 +1,822 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "GrAtlasTextContext.h"
+
+#include "GrDrawContext.h"
+#include "GrDrawTarget.h"
+#include "GrFontScaler.h"
+#include "GrStrokeInfo.h"
+#include "GrTextBlobCache.h"
+#include "GrTexturePriv.h"
+#include "GrTextUtils.h"
+#include "GrVertexBuffer.h"
+
+#include "SkAutoKern.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkDistanceFieldGen.h"
+#include "SkDraw.h"
+#include "SkDrawFilter.h"
+#include "SkDrawProcs.h"
+#include "SkFindAndPlaceGlyph.h"
+#include "SkGlyphCache.h"
+#include "SkGpuDevice.h"
+#include "SkGrPriv.h"
+#include "SkPath.h"
+#include "SkRTConf.h"
+#include "SkStrokeRec.h"
+#include "SkTextBlob.h"
+#include "SkTextMapStateProc.h"
+
+#include "batches/GrAtlasTextBatch.h"
+
+namespace {
+static const int kMinDFFontSize = 18;
+static const int kSmallDFFontSize = 32;
+static const int kSmallDFFontLimit = 32;
+static const int kMediumDFFontSize = 72;
+static const int kMediumDFFontLimit = 72;
+static const int kLargeDFFontSize = 162;
+#ifdef SK_BUILD_FOR_ANDROID
+static const int kLargeDFFontLimit = 384;
+#else
+static const int kLargeDFFontLimit = 2 * kLargeDFFontSize;
+#endif
+};
+
+GrAtlasTextContext::GrAtlasTextContext(GrContext* context, const SkSurfaceProps& surfaceProps)
+ : INHERITED(context, surfaceProps)
+ , fDistanceAdjustTable(new GrDistanceFieldAdjustTable) {
+ // We overallocate vertices in our textblobs based on the assumption that A8 has the greatest
+ // vertexStride
+ static_assert(GrAtlasTextBlob::kGrayTextVASize >= GrAtlasTextBlob::kColorTextVASize &&
+ GrAtlasTextBlob::kGrayTextVASize >= GrAtlasTextBlob::kLCDTextVASize,
+ "vertex_attribute_changed");
+ fCurrStrike = nullptr;
+ fCache = context->getTextBlobCache();
+}
+
+
+GrAtlasTextContext* GrAtlasTextContext::Create(GrContext* context,
+ const SkSurfaceProps& surfaceProps) {
+ return new GrAtlasTextContext(context, surfaceProps);
+}
+
+bool GrAtlasTextContext::canDraw(const SkPaint& skPaint, const SkMatrix& viewMatrix) {
+ return this->canDrawAsDistanceFields(skPaint, viewMatrix) ||
+ !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
+}
+
+GrColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
+ GrColor canonicalColor = paint.computeLuminanceColor();
+ if (lcd) {
+ // This is the correct computation, but there are tons of cases where LCD can be overridden.
+ // For now we just regenerate if any run in a textblob has LCD.
+ // TODO figure out where all of these overrides are and see if we can incorporate that logic
+ // at a higher level *OR* use sRGB
+ SkASSERT(false);
+ //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
+ } else {
+ // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
+ // gamma corrected masks anyways, nor color
+ U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
+ SkColorGetG(canonicalColor),
+ SkColorGetB(canonicalColor));
+ // reduce to our finite number of bits
+ canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
+ }
+ return canonicalColor;
+}
+
+// TODO if this function ever shows up in profiling, then we can compute this value when the
+// textblob is being built and cache it. However, for the time being textblobs mostly only have 1
+// run so this is not a big deal to compute here.
+bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) {
+ SkTextBlobRunIterator it(blob);
+ for (; !it.done(); it.next()) {
+ if (it.isLCD()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline SkGlyphCache* GrAtlasTextContext::setupCache(GrAtlasTextBlob::Run* run,
+ const SkPaint& skPaint,
+ const SkMatrix* viewMatrix,
+ bool noGamma) {
+ skPaint.getScalerContextDescriptor(&run->fDescriptor, fSurfaceProps, viewMatrix, noGamma);
+ run->fTypeface.reset(SkSafeRef(skPaint.getTypeface()));
+ return SkGlyphCache::DetachCache(run->fTypeface, run->fDescriptor.getDesc());
+}
+
+void GrAtlasTextContext::drawTextBlob(GrDrawContext* dc,
+ const GrClip& clip, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix, const SkTextBlob* blob,
+ SkScalar x, SkScalar y,
+ SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
+ // If we have been abandoned, then don't draw
+ if (fContext->abandoned()) {
+ return;
+ }
+
+ SkAutoTUnref<GrAtlasTextBlob> cacheBlob;
+ SkMaskFilter::BlurRec blurRec;
+ GrAtlasTextBlob::Key key;
+ // It might be worth caching these things, but its not clear at this time
+ // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
+ const SkMaskFilter* mf = skPaint.getMaskFilter();
+ bool canCache = !(skPaint.getPathEffect() ||
+ (mf && !mf->asABlur(&blurRec)) ||
+ drawFilter);
+
+ if (canCache) {
+ bool hasLCD = HasLCD(blob);
+
+ // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
+ SkPixelGeometry pixelGeometry = hasLCD ? fSurfaceProps.pixelGeometry() :
+ kUnknown_SkPixelGeometry;
+
+ // TODO we want to figure out a way to be able to use the canonical color on LCD text,
+ // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
+ // ensure we always match the same key
+ GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
+ ComputeCanonicalColor(skPaint, hasLCD);
+
+ key.fPixelGeometry = pixelGeometry;
+ key.fUniqueID = blob->uniqueID();
+ key.fStyle = skPaint.getStyle();
+ key.fHasBlur = SkToBool(mf);
+ key.fCanonicalColor = canonicalColor;
+ cacheBlob.reset(SkSafeRef(fCache->find(key)));
+ }
+
+ SkScalar transX = 0.f;
+ SkScalar transY = 0.f;
+
+ // Though for the time being runs in the textblob can override the paint, they only touch font
+ // info.
+ GrPaint grPaint;
+ if (!SkPaintToGrPaint(fContext, skPaint, viewMatrix, &grPaint)) {
+ return;
+ }
+
+ if (cacheBlob) {
+ if (cacheBlob->mustRegenerate(&transX, &transY, skPaint, grPaint.getColor(), blurRec,
+ viewMatrix, x, y)) {
+ // We have to remake the blob because changes may invalidate our masks.
+ // TODO we could probably get away reuse most of the time if the pointer is unique,
+ // but we'd have to clear the subrun information
+ fCache->remove(cacheBlob);
+ cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint,
+ GrAtlasTextBlob::kGrayTextVASize)));
+ this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix,
+ blob, x, y, drawFilter, clip);
+ } else {
+ fCache->makeMRU(cacheBlob);
+#ifdef CACHE_SANITY_CHECK
+ {
+ int glyphCount = 0;
+ int runCount = 0;
+ GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob);
+ SkAutoTUnref<GrAtlasTextBlob> sanityBlob(fCache->createBlob(glyphCount, runCount,
+ kGrayTextVASize));
+ GrTextBlobCache::SetupCacheBlobKey(sanityBlob, key, blurRec, skPaint);
+ this->regenerateTextBlob(sanityBlob, skPaint, grPaint.getColor(), viewMatrix,
+ blob, x, y, drawFilter, clip);
+ GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
+ }
+
+#endif
+ }
+ } else {
+ if (canCache) {
+ cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint,
+ GrAtlasTextBlob::kGrayTextVASize)));
+ } else {
+ cacheBlob.reset(fCache->createBlob(blob, GrAtlasTextBlob::kGrayTextVASize));
+ }
+ this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix,
+ blob, x, y, drawFilter, clip);
+ }
+
+ cacheBlob->flushCached(fContext, dc, blob, fSurfaceProps, fDistanceAdjustTable, skPaint,
+ grPaint, drawFilter, clip, viewMatrix, clipBounds, x, y, transX, transY);
+}
+
+inline bool GrAtlasTextContext::canDrawAsDistanceFields(const SkPaint& skPaint,
+ const SkMatrix& viewMatrix) {
+ // TODO: support perspective (need getMaxScale replacement)
+ if (viewMatrix.hasPerspective()) {
+ return false;
+ }
+
+ SkScalar maxScale = viewMatrix.getMaxScale();
+ SkScalar scaledTextSize = maxScale*skPaint.getTextSize();
+ // Hinted text looks far better at small resolutions
+ // Scaling up beyond 2x yields undesireable artifacts
+ if (scaledTextSize < kMinDFFontSize || scaledTextSize > kLargeDFFontLimit) {
+ return false;
+ }
+
+ bool useDFT = fSurfaceProps.isUseDeviceIndependentFonts();
+#if SK_FORCE_DISTANCE_FIELD_TEXT
+ useDFT = true;
+#endif
+
+ if (!useDFT && scaledTextSize < kLargeDFFontSize) {
+ return false;
+ }
+
+ // rasterizers and mask filters modify alpha, which doesn't
+ // translate well to distance
+ if (skPaint.getRasterizer() || skPaint.getMaskFilter() ||
+ !fContext->caps()->shaderCaps()->shaderDerivativeSupport()) {
+ return false;
+ }
+
+ // TODO: add some stroking support
+ if (skPaint.getStyle() != SkPaint::kFill_Style) {
+ return false;
+ }
+
+ return true;
+}
+
+void GrAtlasTextContext::regenerateTextBlob(GrAtlasTextBlob* cacheBlob,
+ const SkPaint& skPaint, GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkTextBlob* blob, SkScalar x, SkScalar y,
+ SkDrawFilter* drawFilter,
+ const GrClip& clip) {
+ // The color here is the GrPaint color, and it is used to determine whether we
+ // have to regenerate LCD text blobs.
+ // We use this color vs the SkPaint color because it has the colorfilter applied.
+ cacheBlob->fPaintColor = color;
+ cacheBlob->fViewMatrix = viewMatrix;
+ cacheBlob->fX = x;
+ cacheBlob->fY = y;
+
+ // Regenerate textblob
+ SkPaint runPaint = skPaint;
+ SkTextBlobRunIterator it(blob);
+ for (int run = 0; !it.done(); it.next(), run++) {
+ int glyphCount = it.glyphCount();
+ size_t textLen = glyphCount * sizeof(uint16_t);
+ const SkPoint& offset = it.offset();
+ // applyFontToPaint() always overwrites the exact same attributes,
+ // so it is safe to not re-seed the paint for this reason.
+ it.applyFontToPaint(&runPaint);
+
+ if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
+ // A false return from filter() means we should abort the current draw.
+ runPaint = skPaint;
+ continue;
+ }
+
+ runPaint.setFlags(FilterTextFlags(fSurfaceProps, runPaint));
+
+ cacheBlob->push_back_run(run);
+
+ if (this->canDrawAsDistanceFields(runPaint, viewMatrix)) {
+ cacheBlob->setHasDistanceField();
+ SkPaint dfPaint = runPaint;
+ SkScalar textRatio;
+ this->initDistanceFieldPaint(cacheBlob, &dfPaint, &textRatio, viewMatrix);
+ Run& runIdx = cacheBlob->fRuns[run];
+ PerSubRunInfo& subRun = runIdx.fSubRunInfo.back();
+ subRun.setUseLCDText(runPaint.isLCDRenderText());
+ subRun.setDrawAsDistanceFields();
+
+ SkTDArray<char> fallbackTxt;
+ SkTDArray<SkScalar> fallbackPos;
+ SkPoint dfOffset;
+ int scalarsPerPosition = 2;
+ switch (it.positioning()) {
+ case SkTextBlob::kDefault_Positioning: {
+ this->internalDrawDFText(cacheBlob, run, dfPaint, color, viewMatrix,
+ (const char *)it.glyphs(), textLen,
+ x + offset.x(), y + offset.y(), textRatio,
+ &fallbackTxt, &fallbackPos, &dfOffset, runPaint);
+ break;
+ }
+ case SkTextBlob::kHorizontal_Positioning: {
+ scalarsPerPosition = 1;
+ dfOffset = SkPoint::Make(x, y + offset.y());
+ this->internalDrawDFPosText(cacheBlob, run, dfPaint, color, viewMatrix,
+ (const char*)it.glyphs(), textLen, it.pos(),
+ scalarsPerPosition, dfOffset, textRatio,
+ &fallbackTxt, &fallbackPos);
+ break;
+ }
+ case SkTextBlob::kFull_Positioning: {
+ dfOffset = SkPoint::Make(x, y);
+ this->internalDrawDFPosText(cacheBlob, run, dfPaint, color, viewMatrix,
+ (const char*)it.glyphs(), textLen, it.pos(),
+ scalarsPerPosition, dfOffset, textRatio,
+ &fallbackTxt, &fallbackPos);
+ break;
+ }
+ }
+ if (fallbackTxt.count()) {
+ this->fallbackDrawPosText(cacheBlob, run, clip, color, runPaint, viewMatrix,
+ fallbackTxt, fallbackPos, scalarsPerPosition, dfOffset);
+ }
+ } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) {
+ cacheBlob->fRuns[run].fDrawAsPaths = true;
+ } else {
+ cacheBlob->setHasBitmap();
+ SkGlyphCache* cache = this->setupCache(&cacheBlob->fRuns[run], runPaint, &viewMatrix,
+ false);
+ switch (it.positioning()) {
+ case SkTextBlob::kDefault_Positioning:
+ GrTextUtils::DrawBmpText(cacheBlob, run, fContext->getBatchFontCache(),
+ cache, runPaint, color, viewMatrix,
+ (const char *)it.glyphs(), textLen,
+ x + offset.x(), y + offset.y());
+ break;
+ case SkTextBlob::kHorizontal_Positioning:
+ GrTextUtils::DrawBmpPosText(cacheBlob, run, fContext->getBatchFontCache(),
+ cache, runPaint, color, viewMatrix,
+ (const char*)it.glyphs(), textLen, it.pos(), 1,
+ SkPoint::Make(x, y + offset.y()));
+ break;
+ case SkTextBlob::kFull_Positioning:
+ GrTextUtils::DrawBmpPosText(cacheBlob, run, fContext->getBatchFontCache(),
+ cache, runPaint, color, viewMatrix,
+ (const char*)it.glyphs(), textLen, it.pos(), 2,
+ SkPoint::Make(x, y));
+ break;
+ }
+ SkGlyphCache::AttachCache(cache);
+ }
+
+ if (drawFilter) {
+ // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
+ runPaint = skPaint;
+ }
+ }
+}
+
+inline void GrAtlasTextContext::initDistanceFieldPaint(GrAtlasTextBlob* blob,
+ SkPaint* skPaint,
+ SkScalar* textRatio,
+ const SkMatrix& viewMatrix) {
+ // getMaxScale doesn't support perspective, so neither do we at the moment
+ SkASSERT(!viewMatrix.hasPerspective());
+ SkScalar maxScale = viewMatrix.getMaxScale();
+ SkScalar textSize = skPaint->getTextSize();
+ SkScalar scaledTextSize = textSize;
+ // if we have non-unity scale, we need to choose our base text size
+ // based on the SkPaint's text size multiplied by the max scale factor
+ // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
+ if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
+ scaledTextSize *= maxScale;
+ }
+
+ // We have three sizes of distance field text, and within each size 'bucket' there is a floor
+ // and ceiling. A scale outside of this range would require regenerating the distance fields
+ SkScalar dfMaskScaleFloor;
+ SkScalar dfMaskScaleCeil;
+ if (scaledTextSize <= kSmallDFFontLimit) {
+ dfMaskScaleFloor = kMinDFFontSize;
+ dfMaskScaleCeil = kSmallDFFontLimit;
+ *textRatio = textSize / kSmallDFFontSize;
+ skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize));
+ } else if (scaledTextSize <= kMediumDFFontLimit) {
+ dfMaskScaleFloor = kSmallDFFontLimit;
+ dfMaskScaleCeil = kMediumDFFontLimit;
+ *textRatio = textSize / kMediumDFFontSize;
+ skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize));
+ } else {
+ dfMaskScaleFloor = kMediumDFFontLimit;
+ dfMaskScaleCeil = kLargeDFFontLimit;
+ *textRatio = textSize / kLargeDFFontSize;
+ skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize));
+ }
+
+ // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
+ // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale
+ // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
+ // tolerate before we'd have to move to a large mip size. When we actually test these values
+ // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
+ // against these values to decide if we can reuse or not(ie, will a given scale change our mip
+ // level)
+ SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
+ blob->fMaxMinScale = SkMaxScalar(dfMaskScaleFloor / scaledTextSize, blob->fMaxMinScale);
+ blob->fMinMaxScale = SkMinScalar(dfMaskScaleCeil / scaledTextSize, blob->fMinMaxScale);
+
+ skPaint->setLCDRenderText(false);
+ skPaint->setAutohinted(false);
+ skPaint->setHinting(SkPaint::kNormal_Hinting);
+ skPaint->setSubpixelText(true);
+}
+
+inline void GrAtlasTextContext::fallbackDrawPosText(GrAtlasTextBlob* blob,
+ int runIndex,
+ const GrClip& clip,
+ GrColor color,
+ const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const SkTDArray<char>& fallbackTxt,
+ const SkTDArray<SkScalar>& fallbackPos,
+ int scalarsPerPosition,
+ const SkPoint& offset) {
+ SkASSERT(fallbackTxt.count());
+ blob->setHasBitmap();
+ Run& run = blob->fRuns[runIndex];
+ // Push back a new subrun to fill and set the override descriptor
+ run.push_back();
+ run.fOverrideDescriptor.reset(new SkAutoDescriptor);
+ skPaint.getScalerContextDescriptor(run.fOverrideDescriptor,
+ fSurfaceProps, &viewMatrix, false);
+ SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface,
+ run.fOverrideDescriptor->getDesc());
+ GrTextUtils::DrawBmpPosText(blob, runIndex, fContext->getBatchFontCache(), cache, skPaint,
+ color, viewMatrix, fallbackTxt.begin(), fallbackTxt.count(),
+ fallbackPos.begin(), scalarsPerPosition, offset);
+ SkGlyphCache::AttachCache(cache);
+}
+
+inline GrAtlasTextBlob*
+GrAtlasTextContext::setupDFBlob(int glyphCount, const SkPaint& origPaint,
+ const SkMatrix& viewMatrix, SkPaint* dfPaint,
+ SkScalar* textRatio) {
+ GrAtlasTextBlob* blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBlob::kGrayTextVASize);
+
+ *dfPaint = origPaint;
+ this->initDistanceFieldPaint(blob, dfPaint, textRatio, viewMatrix);
+ blob->fViewMatrix = viewMatrix;
+ Run& run = blob->fRuns[0];
+ PerSubRunInfo& subRun = run.fSubRunInfo.back();
+ subRun.setUseLCDText(origPaint.isLCDRenderText());
+ subRun.setDrawAsDistanceFields();
+
+ return blob;
+}
+
+inline GrAtlasTextBlob*
+GrAtlasTextContext::createDrawTextBlob(const GrClip& clip,
+ const GrPaint& paint, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
+ int glyphCount = skPaint.countText(text, byteLength);
+
+ GrAtlasTextBlob* blob;
+ if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) {
+ SkPaint dfPaint;
+ SkScalar textRatio;
+ blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &dfPaint, &textRatio);
+
+ SkTDArray<char> fallbackTxt;
+ SkTDArray<SkScalar> fallbackPos;
+ SkPoint offset;
+ this->internalDrawDFText(blob, 0, dfPaint, paint.getColor(), viewMatrix, text,
+ byteLength, x, y, textRatio, &fallbackTxt, &fallbackPos,
+ &offset, skPaint);
+ if (fallbackTxt.count()) {
+ this->fallbackDrawPosText(blob, 0, clip, paint.getColor(), skPaint, viewMatrix,
+ fallbackTxt, fallbackPos, 2, offset);
+ }
+ } else {
+ blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBlob::kGrayTextVASize);
+ blob->fViewMatrix = viewMatrix;
+
+ SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false);
+ GrTextUtils::DrawBmpText(blob, 0, fContext->getBatchFontCache(), cache, skPaint,
+ paint.getColor(), viewMatrix, text, byteLength, x, y);
+ SkGlyphCache::AttachCache(cache);
+ }
+ return blob;
+}
+
+inline GrAtlasTextBlob*
+GrAtlasTextContext::createDrawPosTextBlob(const GrClip& clip,
+ const GrPaint& paint, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& regionClipBounds) {
+ int glyphCount = skPaint.countText(text, byteLength);
+
+ GrAtlasTextBlob* blob;
+ if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) {
+ SkPaint dfPaint;
+ SkScalar textRatio;
+ blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &dfPaint, &textRatio);
+
+ SkTDArray<char> fallbackTxt;
+ SkTDArray<SkScalar> fallbackPos;
+ this->internalDrawDFPosText(blob, 0, dfPaint, paint.getColor(), viewMatrix, text,
+ byteLength, pos, scalarsPerPosition, offset,
+ textRatio, &fallbackTxt, &fallbackPos);
+ if (fallbackTxt.count()) {
+ this->fallbackDrawPosText(blob, 0, clip, paint.getColor(), skPaint, viewMatrix,
+ fallbackTxt, fallbackPos, scalarsPerPosition, offset);
+ }
+ } else {
+ blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBlob::kGrayTextVASize);
+ blob->fViewMatrix = viewMatrix;
+ SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false);
+ GrTextUtils::DrawBmpPosText(blob, 0, fContext->getBatchFontCache(), cache, skPaint,
+ paint.getColor(), viewMatrix, text,
+ byteLength, pos, scalarsPerPosition, offset);
+ SkGlyphCache::AttachCache(cache);
+ }
+ return blob;
+}
+
+void GrAtlasTextContext::onDrawText(GrDrawContext* dc,
+ const GrClip& clip,
+ const GrPaint& paint, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
+ SkAutoTUnref<GrAtlasTextBlob> blob(
+ this->createDrawTextBlob(clip, paint, skPaint, viewMatrix,
+ text, byteLength, x, y, regionClipBounds));
+ blob->flushThrowaway(fContext, dc, fSurfaceProps, fDistanceAdjustTable, skPaint, paint,
+ clip, regionClipBounds);
+}
+
+void GrAtlasTextContext::onDrawPosText(GrDrawContext* dc,
+ const GrClip& clip,
+ const GrPaint& paint, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& regionClipBounds) {
+ SkAutoTUnref<GrAtlasTextBlob> blob(
+ this->createDrawPosTextBlob(clip, paint, skPaint, viewMatrix,
+ text, byteLength,
+ pos, scalarsPerPosition,
+ offset, regionClipBounds));
+
+ blob->flushThrowaway(fContext, dc, fSurfaceProps, fDistanceAdjustTable, skPaint, paint, clip,
+ regionClipBounds);
+}
+
+void GrAtlasTextContext::internalDrawDFText(GrAtlasTextBlob* blob, int runIndex,
+ const SkPaint& skPaint, GrColor color,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y,
+ SkScalar textRatio,
+ SkTDArray<char>* fallbackTxt,
+ SkTDArray<SkScalar>* fallbackPos,
+ SkPoint* offset,
+ const SkPaint& origPaint) {
+ SkASSERT(byteLength == 0 || text != nullptr);
+
+ // nothing to draw
+ if (text == nullptr || byteLength == 0) {
+ return;
+ }
+
+ SkDrawCacheProc glyphCacheProc = origPaint.getDrawCacheProc();
+ SkAutoDescriptor desc;
+ origPaint.getScalerContextDescriptor(&desc, fSurfaceProps, nullptr, true);
+ SkGlyphCache* origPaintCache = SkGlyphCache::DetachCache(origPaint.getTypeface(),
+ desc.getDesc());
+
+ SkTArray<SkScalar> positions;
+
+ const char* textPtr = text;
+ SkFixed stopX = 0;
+ SkFixed stopY = 0;
+ SkFixed origin = 0;
+ switch (origPaint.getTextAlign()) {
+ case SkPaint::kRight_Align: origin = SK_Fixed1; break;
+ case SkPaint::kCenter_Align: origin = SK_FixedHalf; break;
+ case SkPaint::kLeft_Align: origin = 0; break;
+ }
+
+ SkAutoKern autokern;
+ const char* stop = text + byteLength;
+ while (textPtr < stop) {
+ // don't need x, y here, since all subpixel variants will have the
+ // same advance
+ const SkGlyph& glyph = glyphCacheProc(origPaintCache, &textPtr, 0, 0);
+
+ SkFixed width = glyph.fAdvanceX + autokern.adjust(glyph);
+ positions.push_back(SkFixedToScalar(stopX + SkFixedMul(origin, width)));
+
+ SkFixed height = glyph.fAdvanceY;
+ positions.push_back(SkFixedToScalar(stopY + SkFixedMul(origin, height)));
+
+ stopX += width;
+ stopY += height;
+ }
+ SkASSERT(textPtr == stop);
+
+ SkGlyphCache::AttachCache(origPaintCache);
+
+ // now adjust starting point depending on alignment
+ SkScalar alignX = SkFixedToScalar(stopX);
+ SkScalar alignY = SkFixedToScalar(stopY);
+ if (origPaint.getTextAlign() == SkPaint::kCenter_Align) {
+ alignX = SkScalarHalf(alignX);
+ alignY = SkScalarHalf(alignY);
+ } else if (origPaint.getTextAlign() == SkPaint::kLeft_Align) {
+ alignX = 0;
+ alignY = 0;
+ }
+ x -= alignX;
+ y -= alignY;
+ *offset = SkPoint::Make(x, y);
+
+ this->internalDrawDFPosText(blob, runIndex, skPaint, color, viewMatrix, text, byteLength,
+ positions.begin(), 2, *offset, textRatio, fallbackTxt,
+ fallbackPos);
+}
+
+void GrAtlasTextContext::internalDrawDFPosText(GrAtlasTextBlob* blob, int runIndex,
+ const SkPaint& skPaint, GrColor color,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset,
+ SkScalar textRatio,
+ SkTDArray<char>* fallbackTxt,
+ SkTDArray<SkScalar>* fallbackPos) {
+
+ SkASSERT(byteLength == 0 || text != nullptr);
+ SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
+
+ // nothing to draw
+ if (text == nullptr || byteLength == 0) {
+ return;
+ }
+
+ fCurrStrike = nullptr;
+
+ SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
+ SkGlyphCache* cache = this->setupCache(&blob->fRuns[runIndex], skPaint, nullptr, true);
+ GrFontScaler* fontScaler = GetGrFontScaler(cache);
+
+ const char* stop = text + byteLength;
+
+ if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
+ while (text < stop) {
+ const char* lastText = text;
+ // the last 2 parameters are ignored
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+ if (glyph.fWidth) {
+ SkScalar x = offset.x() + pos[0];
+ SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0);
+
+ if (!this->dfAppendGlyph(blob,
+ runIndex,
+ glyph,
+ x, y, color, fontScaler,
+ textRatio, viewMatrix)) {
+ // couldn't append, send to fallback
+ fallbackTxt->append(SkToInt(text-lastText), lastText);
+ *fallbackPos->append() = pos[0];
+ if (2 == scalarsPerPosition) {
+ *fallbackPos->append() = pos[1];
+ }
+ }
+ }
+ pos += scalarsPerPosition;
+ }
+ } else {
+ SkScalar alignMul = SkPaint::kCenter_Align == skPaint.getTextAlign() ? SK_ScalarHalf
+ : SK_Scalar1;
+ while (text < stop) {
+ const char* lastText = text;
+ // the last 2 parameters are ignored
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+ if (glyph.fWidth) {
+ SkScalar x = offset.x() + pos[0];
+ SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0);
+
+ SkScalar advanceX = SkFixedToScalar(glyph.fAdvanceX) * alignMul * textRatio;
+ SkScalar advanceY = SkFixedToScalar(glyph.fAdvanceY) * alignMul * textRatio;
+
+ if (!this->dfAppendGlyph(blob,
+ runIndex,
+ glyph,
+ x - advanceX, y - advanceY, color,
+ fontScaler,
+ textRatio,
+ viewMatrix)) {
+ // couldn't append, send to fallback
+ fallbackTxt->append(SkToInt(text-lastText), lastText);
+ *fallbackPos->append() = pos[0];
+ if (2 == scalarsPerPosition) {
+ *fallbackPos->append() = pos[1];
+ }
+ }
+ }
+ pos += scalarsPerPosition;
+ }
+ }
+
+ SkGlyphCache::AttachCache(cache);
+}
+
+bool GrAtlasTextContext::dfAppendGlyph(GrAtlasTextBlob* blob, int runIndex,
+ const SkGlyph& skGlyph,
+ SkScalar sx, SkScalar sy, GrColor color,
+ GrFontScaler* scaler,
+ SkScalar textRatio, const SkMatrix& viewMatrix) {
+ if (!fCurrStrike) {
+ fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler);
+ }
+
+ GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(),
+ skGlyph.getSubXFixed(),
+ skGlyph.getSubYFixed(),
+ GrGlyph::kDistance_MaskStyle);
+ GrGlyph* glyph = fCurrStrike->getGlyph(skGlyph, id, scaler);
+ if (!glyph) {
+ return true;
+ }
+
+ // fallback to color glyph support
+ if (kA8_GrMaskFormat != glyph->fMaskFormat) {
+ return false;
+ }
+
+ SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset);
+ SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset);
+ SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceFieldInset);
+ SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFieldInset);
+
+ SkScalar scale = textRatio;
+ dx *= scale;
+ dy *= scale;
+ width *= scale;
+ height *= scale;
+ sx += dx;
+ sy += dy;
+ SkRect glyphRect = SkRect::MakeXYWH(sx, sy, width, height);
+
+ blob->appendGlyph(runIndex, glyphRect, color, fCurrStrike, glyph, scaler, skGlyph,
+ sx - dx, sy - dy, scale, true);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+DRAW_BATCH_TEST_DEFINE(TextBlobBatch) {
+ static uint32_t gContextID = SK_InvalidGenID;
+ static GrAtlasTextContext* gTextContext = nullptr;
+ static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
+
+ if (context->uniqueID() != gContextID) {
+ gContextID = context->uniqueID();
+ delete gTextContext;
+
+ // We don't yet test the fall back to paths in the GrTextContext base class. This is mostly
+ // because we don't really want to have a gpu device here.
+ // We enable distance fields by twiddling a knob on the paint
+ gTextContext = GrAtlasTextContext::Create(context, gSurfaceProps);
+ }
+
+ // Setup dummy SkPaint / GrPaint
+ GrColor color = GrRandomColor(random);
+ SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
+ SkPaint skPaint;
+ skPaint.setColor(color);
+ skPaint.setLCDRenderText(random->nextBool());
+ skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
+ skPaint.setSubpixelText(random->nextBool());
+
+ GrPaint grPaint;
+ if (!SkPaintToGrPaint(context, skPaint, viewMatrix, &grPaint)) {
+ SkFAIL("couldn't convert paint\n");
+ }
+
+ const char* text = "The quick brown fox jumps over the lazy dog.";
+ int textLen = (int)strlen(text);
+
+ // Setup clip
+ GrClip clip;
+ SkIRect noClip = SkIRect::MakeLargest();
+
+ // right now we don't handle textblobs, nor do we handle drawPosText. Since we only
+ // intend to test the batch with this unit test, that is okay.
+ SkAutoTUnref<GrAtlasTextBlob> blob(
+ gTextContext->createDrawTextBlob(clip, grPaint, skPaint, viewMatrix, text,
+ static_cast<size_t>(textLen), 0, 0, noClip));
+
+ SkScalar transX = static_cast<SkScalar>(random->nextU());
+ SkScalar transY = static_cast<SkScalar>(random->nextU());
+ const GrAtlasTextBlob::Run::SubRunInfo& info = blob->fRuns[0].fSubRunInfo[0];
+ return blob->createBatch(info, textLen, 0, 0, color, transX, transY, skPaint,
+ gSurfaceProps, gTextContext->dfAdjustTable(),
+ context->getBatchFontCache());
+}
+
+#endif
diff --git a/src/gpu/text/GrAtlasTextContext.h b/src/gpu/text/GrAtlasTextContext.h
new file mode 100644
index 0000000000..47f05404de
--- /dev/null
+++ b/src/gpu/text/GrAtlasTextContext.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrAtlasTextContext_DEFINED
+#define GrAtlasTextContext_DEFINED
+
+#include "GrTextContext.h"
+
+#include "GrAtlasTextBlob.h"
+#include "GrDistanceFieldAdjustTable.h"
+#include "GrGeometryProcessor.h"
+#include "SkTextBlobRunIterator.h"
+
+#ifdef GR_TEST_UTILS
+#include "GrBatchTest.h"
+#endif
+
+class GrDrawBatch;
+class GrDrawContext;
+class GrDrawTarget;
+class GrPipelineBuilder;
+class GrTextBlobCache;
+class SkGlyph;
+
+/*
+ * This class implements GrTextContext using standard bitmap fonts, and can also process textblobs.
+ */
+class GrAtlasTextContext : public GrTextContext {
+public:
+ static GrAtlasTextContext* Create(GrContext*, const SkSurfaceProps&);
+
+private:
+ GrAtlasTextContext(GrContext*, const SkSurfaceProps&);
+ ~GrAtlasTextContext() override {}
+
+ bool canDraw(const SkPaint&, const SkMatrix& viewMatrix) override;
+
+ void onDrawText(GrDrawContext*, const GrClip&, const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix, const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkIRect& regionClipBounds) override;
+ void onDrawPosText(GrDrawContext*, const GrClip&, const GrPaint&,
+ const SkPaint&, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& regionClipBounds) override;
+ void drawTextBlob(GrDrawContext*, const GrClip&, const SkPaint&,
+ const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y,
+ SkDrawFilter*, const SkIRect& clipBounds) override;
+
+ typedef GrAtlasTextBlob::Run Run;
+ typedef Run::SubRunInfo PerSubRunInfo;
+
+ inline bool canDrawAsDistanceFields(const SkPaint&, const SkMatrix& viewMatrix);
+ GrAtlasTextBlob* setupDFBlob(int glyphCount, const SkPaint& origPaint,
+ const SkMatrix& viewMatrix, SkPaint* dfPaint,
+ SkScalar* textRatio);
+ void bmpAppendGlyph(GrAtlasTextBlob*, int runIndex, const SkGlyph&, int left, int top,
+ GrColor color, GrFontScaler*);
+ bool dfAppendGlyph(GrAtlasTextBlob*, int runIndex, const SkGlyph&, SkScalar sx, SkScalar sy,
+ GrColor color, GrFontScaler*, SkScalar textRatio,
+ const SkMatrix& viewMatrix);
+
+ // A helper for drawing BitmapText in a run of distance fields
+ inline void fallbackDrawPosText(GrAtlasTextBlob*, int runIndex,
+ const GrClip&, GrColor color,
+ const SkPaint&, const SkMatrix& viewMatrix,
+ const SkTDArray<char>& fallbackTxt,
+ const SkTDArray<SkScalar>& fallbackPos,
+ int scalarsPerPosition,
+ const SkPoint& offset);
+
+ void internalDrawDFText(GrAtlasTextBlob*, int runIndex, const SkPaint&,
+ GrColor color, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y,
+ SkScalar textRatio,
+ SkTDArray<char>* fallbackTxt,
+ SkTDArray<SkScalar>* fallbackPos,
+ SkPoint* offset, const SkPaint& origPaint);
+ void internalDrawDFPosText(GrAtlasTextBlob*, int runIndex, const SkPaint&,
+ GrColor color, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset,
+ SkScalar textRatio,
+ SkTDArray<char>* fallbackTxt,
+ SkTDArray<SkScalar>* fallbackPos);
+
+ // sets up the descriptor on the blob and returns a detached cache. Client must attach
+ inline static GrColor ComputeCanonicalColor(const SkPaint&, bool lcd);
+ inline SkGlyphCache* setupCache(Run*, const SkPaint&, const SkMatrix* viewMatrix, bool noGamma);
+ void regenerateTextBlob(GrAtlasTextBlob* bmp, const SkPaint& skPaint, GrColor,
+ const SkMatrix& viewMatrix,
+ const SkTextBlob* blob, SkScalar x, SkScalar y,
+ SkDrawFilter* drawFilter,
+ const GrClip&);
+ inline static bool HasLCD(const SkTextBlob*);
+ inline void initDistanceFieldPaint(GrAtlasTextBlob*, SkPaint*, SkScalar* textRatio,
+ const SkMatrix&);
+
+ // Test methods
+ // TODO this is really ugly. It'd be much nicer if positioning could be moved to batch
+ inline GrAtlasTextBlob* createDrawTextBlob(const GrClip&, const GrPaint&,
+ const SkPaint&, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y,
+ const SkIRect& regionClipBounds);
+ inline GrAtlasTextBlob* createDrawPosTextBlob(const GrClip&, const GrPaint&,
+ const SkPaint&, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset,
+ const SkIRect& regionClipBounds);
+ const GrDistanceFieldAdjustTable* dfAdjustTable() const { return fDistanceAdjustTable; }
+
+ GrBatchTextStrike* fCurrStrike;
+ GrTextBlobCache* fCache;
+ SkAutoTUnref<const GrDistanceFieldAdjustTable> fDistanceAdjustTable;
+
+#ifdef GR_TEST_UTILS
+ DRAW_BATCH_TEST_FRIEND(TextBlobBatch);
+#endif
+
+ typedef GrTextContext INHERITED;
+};
+
+#endif
diff --git a/src/gpu/text/GrBatchFontCache.cpp b/src/gpu/text/GrBatchFontCache.cpp
new file mode 100644
index 0000000000..ad76e4d71f
--- /dev/null
+++ b/src/gpu/text/GrBatchFontCache.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrBatchFontCache.h"
+#include "GrContext.h"
+#include "GrGpu.h"
+#include "GrRectanizer.h"
+#include "GrResourceProvider.h"
+#include "GrSurfacePriv.h"
+#include "SkString.h"
+
+#include "SkDistanceFieldGen.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool GrBatchFontCache::initAtlas(GrMaskFormat format) {
+ int index = MaskFormatToAtlasIndex(format);
+ if (!fAtlases[index]) {
+ GrPixelConfig config = MaskFormatToPixelConfig(format);
+ int width = fAtlasConfigs[index].fWidth;
+ int height = fAtlasConfigs[index].fHeight;
+ int numPlotsX = fAtlasConfigs[index].numPlotsX();
+ int numPlotsY = fAtlasConfigs[index].numPlotsY();
+
+ fAtlases[index] =
+ fContext->resourceProvider()->createAtlas(config, width, height,
+ numPlotsX, numPlotsY,
+ &GrBatchFontCache::HandleEviction,
+ (void*)this);
+ if (!fAtlases[index]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+GrBatchFontCache::GrBatchFontCache(GrContext* context)
+ : fContext(context)
+ , fPreserveStrike(nullptr) {
+ for (int i = 0; i < kMaskFormatCount; ++i) {
+ fAtlases[i] = nullptr;
+ }
+
+ // setup default atlas configs
+ fAtlasConfigs[kA8_GrMaskFormat].fWidth = 2048;
+ fAtlasConfigs[kA8_GrMaskFormat].fHeight = 2048;
+ fAtlasConfigs[kA8_GrMaskFormat].fPlotWidth = 512;
+ fAtlasConfigs[kA8_GrMaskFormat].fPlotHeight = 256;
+
+ fAtlasConfigs[kA565_GrMaskFormat].fWidth = 1024;
+ fAtlasConfigs[kA565_GrMaskFormat].fHeight = 2048;
+ fAtlasConfigs[kA565_GrMaskFormat].fPlotWidth = 256;
+ fAtlasConfigs[kA565_GrMaskFormat].fPlotHeight = 256;
+
+ fAtlasConfigs[kARGB_GrMaskFormat].fWidth = 1024;
+ fAtlasConfigs[kARGB_GrMaskFormat].fHeight = 2048;
+ fAtlasConfigs[kARGB_GrMaskFormat].fPlotWidth = 256;
+ fAtlasConfigs[kARGB_GrMaskFormat].fPlotHeight = 256;
+}
+
+GrBatchFontCache::~GrBatchFontCache() {
+ SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
+ while (!iter.done()) {
+ (*iter).fIsAbandoned = true;
+ (*iter).unref();
+ ++iter;
+ }
+ for (int i = 0; i < kMaskFormatCount; ++i) {
+ delete fAtlases[i];
+ }
+}
+
+void GrBatchFontCache::freeAll() {
+ SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
+ while (!iter.done()) {
+ (*iter).fIsAbandoned = true;
+ (*iter).unref();
+ ++iter;
+ }
+ fCache.rewind();
+ for (int i = 0; i < kMaskFormatCount; ++i) {
+ delete fAtlases[i];
+ fAtlases[i] = nullptr;
+ }
+}
+
+void GrBatchFontCache::HandleEviction(GrBatchAtlas::AtlasID id, void* ptr) {
+ GrBatchFontCache* fontCache = reinterpret_cast<GrBatchFontCache*>(ptr);
+
+ SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fontCache->fCache);
+ for (; !iter.done(); ++iter) {
+ GrBatchTextStrike* strike = &*iter;
+ strike->removeID(id);
+
+ // clear out any empty strikes. We will preserve the strike whose call to addToAtlas
+ // triggered the eviction
+ if (strike != fontCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) {
+ fontCache->fCache.remove(*(strike->fFontScalerKey));
+ strike->fIsAbandoned = true;
+ strike->unref();
+ }
+ }
+}
+
+void GrBatchFontCache::dump() const {
+ static int gDumpCount = 0;
+ for (int i = 0; i < kMaskFormatCount; ++i) {
+ if (fAtlases[i]) {
+ GrTexture* texture = fAtlases[i]->getTexture();
+ if (texture) {
+ SkString filename;
+#ifdef SK_BUILD_FOR_ANDROID
+ filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
+#else
+ filename.printf("fontcache_%d%d.png", gDumpCount, i);
+#endif
+ texture->surfacePriv().savePixels(filename.c_str());
+ }
+ }
+ }
+ ++gDumpCount;
+}
+
+void GrBatchFontCache::setAtlasSizes_ForTesting(const GrBatchAtlasConfig configs[3]) {
+ // delete any old atlases, this should be safe to do as long as we are not in the middle of a
+ // flush
+ for (int i = 0; i < kMaskFormatCount; i++) {
+ if (fAtlases[i]) {
+ delete fAtlases[i];
+ fAtlases[i] = nullptr;
+ }
+ }
+ memcpy(fAtlasConfigs, configs, sizeof(fAtlasConfigs));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ The text strike is specific to a given font/style/matrix setup, which is
+ represented by the GrHostFontScaler object we are given in getGlyph().
+
+ We map a 32bit glyphID to a GrGlyph record, which in turn points to a
+ atlas and a position within that texture.
+ */
+
+GrBatchTextStrike::GrBatchTextStrike(GrBatchFontCache* cache, const GrFontDescKey* key)
+ : fFontScalerKey(SkRef(key))
+ , fPool(9/*start allocations at 512 bytes*/)
+ , fAtlasedGlyphs(0)
+ , fIsAbandoned(false) {
+
+ fBatchFontCache = cache; // no need to ref, it won't go away before we do
+}
+
+GrBatchTextStrike::~GrBatchTextStrike() {
+ SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
+ while (!iter.done()) {
+ (*iter).free();
+ ++iter;
+ }
+}
+
+GrGlyph* GrBatchTextStrike::generateGlyph(const SkGlyph& skGlyph, GrGlyph::PackedID packed,
+ GrFontScaler* scaler) {
+ SkIRect bounds;
+ if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(packed)) {
+ if (!scaler->getPackedGlyphDFBounds(skGlyph, &bounds)) {
+ return nullptr;
+ }
+ } else {
+ if (!scaler->getPackedGlyphBounds(skGlyph, &bounds)) {
+ return nullptr;
+ }
+ }
+ GrMaskFormat format = scaler->getPackedGlyphMaskFormat(skGlyph);
+
+ GrGlyph* glyph = (GrGlyph*)fPool.alloc(sizeof(GrGlyph));
+ glyph->init(packed, bounds, format);
+ fCache.add(glyph);
+ return glyph;
+}
+
+void GrBatchTextStrike::removeID(GrBatchAtlas::AtlasID id) {
+ SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
+ while (!iter.done()) {
+ if (id == (*iter).fID) {
+ (*iter).fID = GrBatchAtlas::kInvalidAtlasID;
+ fAtlasedGlyphs--;
+ SkASSERT(fAtlasedGlyphs >= 0);
+ }
+ ++iter;
+ }
+}
+
+bool GrBatchTextStrike::addGlyphToAtlas(GrDrawBatch::Target* target,
+ GrGlyph* glyph,
+ GrFontScaler* scaler,
+ GrMaskFormat expectedMaskFormat) {
+ SkASSERT(glyph);
+ SkASSERT(scaler);
+ SkASSERT(fCache.find(glyph->fPackedID));
+
+ SkAutoUnref ar(SkSafeRef(scaler));
+
+ int bytesPerPixel = GrMaskFormatBytesPerPixel(expectedMaskFormat);
+
+ size_t size = glyph->fBounds.area() * bytesPerPixel;
+ SkAutoSMalloc<1024> storage(size);
+
+ const SkGlyph& skGlyph = scaler->grToSkGlyph(glyph->fPackedID);
+ if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)) {
+ if (!scaler->getPackedGlyphDFImage(skGlyph, glyph->width(), glyph->height(),
+ storage.get())) {
+ return false;
+ }
+ } else {
+ if (!scaler->getPackedGlyphImage(skGlyph, glyph->width(), glyph->height(),
+ glyph->width() * bytesPerPixel, expectedMaskFormat,
+ storage.get())) {
+ return false;
+ }
+ }
+
+ bool success = fBatchFontCache->addToAtlas(this, &glyph->fID, target, expectedMaskFormat,
+ glyph->width(), glyph->height(),
+ storage.get(), &glyph->fAtlasLocation);
+ if (success) {
+ SkASSERT(GrBatchAtlas::kInvalidAtlasID != glyph->fID);
+ fAtlasedGlyphs++;
+ }
+ return success;
+}
diff --git a/src/gpu/text/GrBatchFontCache.h b/src/gpu/text/GrBatchFontCache.h
new file mode 100644
index 0000000000..46ab1c8274
--- /dev/null
+++ b/src/gpu/text/GrBatchFontCache.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrBatchFontCache_DEFINED
+#define GrBatchFontCache_DEFINED
+
+#include "GrBatchAtlas.h"
+#include "GrFontScaler.h"
+#include "GrGlyph.h"
+#include "SkGlyph.h"
+#include "SkTDynamicHash.h"
+#include "SkVarAlloc.h"
+
+class GrBatchFontCache;
+class GrGpu;
+
+/**
+ * The GrBatchTextStrike manages a pool of CPU backing memory for GrGlyphs. This backing memory
+ * is indexed by a PackedID and GrFontScaler. The GrFontScaler is what actually creates the mask.
+ */
+class GrBatchTextStrike : public SkNVRefCnt<GrBatchTextStrike> {
+public:
+ GrBatchTextStrike(GrBatchFontCache*, const GrFontDescKey* fontScalerKey);
+ ~GrBatchTextStrike();
+
+ const GrFontDescKey* getFontScalerKey() const { return fFontScalerKey; }
+ GrBatchFontCache* getBatchFontCache() const { return fBatchFontCache; }
+
+ inline GrGlyph* getGlyph(const SkGlyph& skGlyph, GrGlyph::PackedID packed,
+ GrFontScaler* scaler) {
+ GrGlyph* glyph = fCache.find(packed);
+ if (nullptr == glyph) {
+ glyph = this->generateGlyph(skGlyph, packed, scaler);
+ }
+ return glyph;
+ }
+
+ // This variant of the above function is called by TextBatch. At this point, it is possible
+ // that the maskformat of the glyph differs from what we expect. In these cases we will just
+ // draw a clear square.
+ // skbug:4143 crbug:510931
+ inline GrGlyph* getGlyph(GrGlyph::PackedID packed,
+ GrMaskFormat expectedMaskFormat,
+ GrFontScaler* scaler) {
+ GrGlyph* glyph = fCache.find(packed);
+ if (nullptr == glyph) {
+ // We could return this to the caller, but in practice it adds code complexity for
+ // potentially little benefit(ie, if the glyph is not in our font cache, then its not
+ // in the atlas and we're going to be doing a texture upload anyways).
+ const SkGlyph& skGlyph = scaler->grToSkGlyph(packed);
+ glyph = this->generateGlyph(skGlyph, packed, scaler);
+ glyph->fMaskFormat = expectedMaskFormat;
+ }
+ return glyph;
+ }
+
+ // returns true if glyph successfully added to texture atlas, false otherwise. If the glyph's
+ // mask format has changed, then addGlyphToAtlas will draw a clear box. This will almost never
+ // happen.
+ // TODO we can handle some of these cases if we really want to, but the long term solution is to
+ // get the actual glyph image itself when we get the glyph metrics.
+ bool addGlyphToAtlas(GrDrawBatch::Target*, GrGlyph*, GrFontScaler*,
+ GrMaskFormat expectedMaskFormat);
+
+ // testing
+ int countGlyphs() const { return fCache.count(); }
+
+ // remove any references to this plot
+ void removeID(GrBatchAtlas::AtlasID);
+
+ // If a TextStrike is abandoned by the cache, then the caller must get a new strike
+ bool isAbandoned() const { return fIsAbandoned; }
+
+ static const GrFontDescKey& GetKey(const GrBatchTextStrike& ts) {
+ return *(ts.fFontScalerKey);
+ }
+ static uint32_t Hash(const GrFontDescKey& key) {
+ return key.getHash();
+ }
+
+private:
+ SkTDynamicHash<GrGlyph, GrGlyph::PackedID> fCache;
+ SkAutoTUnref<const GrFontDescKey> fFontScalerKey;
+ SkVarAlloc fPool;
+
+ GrBatchFontCache* fBatchFontCache;
+ int fAtlasedGlyphs;
+ bool fIsAbandoned;
+
+ GrGlyph* generateGlyph(const SkGlyph&, GrGlyph::PackedID, GrFontScaler*);
+
+ friend class GrBatchFontCache;
+};
+
+/*
+ * GrBatchFontCache manages strikes which are indexed by a GrFontScaler. These strikes can then be
+ * used to individual Glyph Masks. The GrBatchFontCache also manages GrBatchAtlases, though this is
+ * more or less transparent to the client(aside from atlasGeneration, described below).
+ * Note - we used to initialize the backing atlas for the GrBatchFontCache at initialization time.
+ * However, this caused a regression, even when the GrBatchFontCache was unused. We now initialize
+ * the backing atlases lazily. Its not immediately clear why this improves the situation.
+ */
+class GrBatchFontCache {
+public:
+ GrBatchFontCache(GrContext*);
+ ~GrBatchFontCache();
+ // The user of the cache may hold a long-lived ref to the returned strike. However, actions by
+ // another client of the cache may cause the strike to be purged while it is still reffed.
+ // Therefore, the caller must check GrBatchTextStrike::isAbandoned() if there are other
+ // interactions with the cache since the strike was received.
+ inline GrBatchTextStrike* getStrike(GrFontScaler* scaler) {
+ GrBatchTextStrike* strike = fCache.find(*(scaler->getKey()));
+ if (nullptr == strike) {
+ strike = this->generateStrike(scaler);
+ }
+ return strike;
+ }
+
+ void freeAll();
+
+ // if getTexture returns nullptr, the client must not try to use other functions on the
+ // GrBatchFontCache which use the atlas. This function *must* be called first, before other
+ // functions which use the atlas.
+ GrTexture* getTexture(GrMaskFormat format) {
+ if (this->initAtlas(format)) {
+ return this->getAtlas(format)->getTexture();
+ }
+ return nullptr;
+ }
+
+ bool hasGlyph(GrGlyph* glyph) {
+ SkASSERT(glyph);
+ return this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID);
+ }
+
+ // To ensure the GrBatchAtlas does not evict the Glyph Mask from its texture backing store,
+ // the client must pass in the current batch token along with the GrGlyph.
+ // A BulkUseTokenUpdater is used to manage bulk last use token updating in the Atlas.
+ // For convenience, this function will also set the use token for the current glyph if required
+ // NOTE: the bulk uploader is only valid if the subrun has a valid atlasGeneration
+ void addGlyphToBulkAndSetUseToken(GrBatchAtlas::BulkUseTokenUpdater* updater,
+ GrGlyph* glyph, GrBatchToken token) {
+ SkASSERT(glyph);
+ updater->add(glyph->fID);
+ this->getAtlas(glyph->fMaskFormat)->setLastUseToken(glyph->fID, token);
+ }
+
+ void setUseTokenBulk(const GrBatchAtlas::BulkUseTokenUpdater& updater,
+ GrBatchToken token,
+ GrMaskFormat format) {
+ this->getAtlas(format)->setLastUseTokenBulk(updater, token);
+ }
+
+ // add to texture atlas that matches this format
+ bool addToAtlas(GrBatchTextStrike* strike, GrBatchAtlas::AtlasID* id,
+ GrDrawBatch::Target* target,
+ GrMaskFormat format, int width, int height, const void* image,
+ SkIPoint16* loc) {
+ fPreserveStrike = strike;
+ return this->getAtlas(format)->addToAtlas(id, target, width, height, image, loc);
+ }
+
+ // Some clients may wish to verify the integrity of the texture backing store of the
+ // GrBatchAtlas. The atlasGeneration returned below is a monitonically increasing number which
+ // changes everytime something is removed from the texture backing store.
+ uint64_t atlasGeneration(GrMaskFormat format) const {
+ return this->getAtlas(format)->atlasGeneration();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Functions intended debug only
+ void dump() const;
+
+ void setAtlasSizes_ForTesting(const GrBatchAtlasConfig configs[3]);
+
+private:
+ static GrPixelConfig MaskFormatToPixelConfig(GrMaskFormat format) {
+ static const GrPixelConfig kPixelConfigs[] = {
+ kAlpha_8_GrPixelConfig,
+ kRGB_565_GrPixelConfig,
+ kSkia8888_GrPixelConfig
+ };
+ static_assert(SK_ARRAY_COUNT(kPixelConfigs) == kMaskFormatCount, "array_size_mismatch");
+
+ return kPixelConfigs[format];
+ }
+
+ // There is a 1:1 mapping between GrMaskFormats and atlas indices
+ static int MaskFormatToAtlasIndex(GrMaskFormat format) {
+ static const int sAtlasIndices[] = {
+ kA8_GrMaskFormat,
+ kA565_GrMaskFormat,
+ kARGB_GrMaskFormat,
+ };
+ static_assert(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, "array_size_mismatch");
+
+ SkASSERT(sAtlasIndices[format] < kMaskFormatCount);
+ return sAtlasIndices[format];
+ }
+
+ bool initAtlas(GrMaskFormat);
+
+ GrBatchTextStrike* generateStrike(GrFontScaler* scaler) {
+ GrBatchTextStrike* strike = new GrBatchTextStrike(this, scaler->getKey());
+ fCache.add(strike);
+ return strike;
+ }
+
+ GrBatchAtlas* getAtlas(GrMaskFormat format) const {
+ int atlasIndex = MaskFormatToAtlasIndex(format);
+ SkASSERT(fAtlases[atlasIndex]);
+ return fAtlases[atlasIndex];
+ }
+
+ static void HandleEviction(GrBatchAtlas::AtlasID, void*);
+
+ GrContext* fContext;
+ SkTDynamicHash<GrBatchTextStrike, GrFontDescKey> fCache;
+ GrBatchAtlas* fAtlases[kMaskFormatCount];
+ GrBatchTextStrike* fPreserveStrike;
+ GrBatchAtlasConfig fAtlasConfigs[kMaskFormatCount];
+};
+
+#endif
diff --git a/src/gpu/text/GrDistanceFieldAdjustTable.cpp b/src/gpu/text/GrDistanceFieldAdjustTable.cpp
new file mode 100644
index 0000000000..1c5aeceb80
--- /dev/null
+++ b/src/gpu/text/GrDistanceFieldAdjustTable.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrDistanceFieldAdjustTable.h"
+
+#include "SkScalerContext.h"
+
+SkDEBUGCODE(static const int kExpectedDistanceAdjustTableSize = 8;)
+
+void GrDistanceFieldAdjustTable::buildDistanceAdjustTable() {
+ // This is used for an approximation of the mask gamma hack, used by raster and bitmap
+ // text. The mask gamma hack is based off of guessing what the blend color is going to
+ // be, and adjusting the mask so that when run through the linear blend will
+ // produce the value closest to the desired result. However, in practice this means
+ // that the 'adjusted' mask is just increasing or decreasing the coverage of
+ // the mask depending on what it is thought it will blit against. For black (on
+ // assumed white) this means that coverages are decreased (on a curve). For white (on
+ // assumed black) this means that coverages are increased (on a a curve). At
+ // middle (perceptual) gray (which could be blit against anything) the coverages
+ // remain the same.
+ //
+ // The idea here is that instead of determining the initial (real) coverage and
+ // then adjusting that coverage, we determine an adjusted coverage directly by
+ // essentially manipulating the geometry (in this case, the distance to the glyph
+ // edge). So for black (on assumed white) this thins a bit; for white (on
+ // assumed black) this fake bolds the geometry a bit.
+ //
+ // The distance adjustment is calculated by determining the actual coverage value which
+ // when fed into in the mask gamma table gives us an 'adjusted coverage' value of 0.5. This
+ // actual coverage value (assuming it's between 0 and 1) corresponds to a distance from the
+ // actual edge. So by subtracting this distance adjustment and computing without the
+ // the coverage adjustment we should get 0.5 coverage at the same point.
+ //
+ // This has several implications:
+ // For non-gray lcd smoothed text, each subpixel essentially is using a
+ // slightly different geometry.
+ //
+ // For black (on assumed white) this may not cover some pixels which were
+ // previously covered; however those pixels would have been only slightly
+ // covered and that slight coverage would have been decreased anyway. Also, some pixels
+ // which were previously fully covered may no longer be fully covered.
+ //
+ // For white (on assumed black) this may cover some pixels which weren't
+ // previously covered at all.
+
+ int width, height;
+ size_t size;
+
+#ifdef SK_GAMMA_CONTRAST
+ SkScalar contrast = SK_GAMMA_CONTRAST;
+#else
+ SkScalar contrast = 0.5f;
+#endif
+ SkScalar paintGamma = SK_GAMMA_EXPONENT;
+ SkScalar deviceGamma = SK_GAMMA_EXPONENT;
+
+ size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
+ &width, &height);
+
+ SkASSERT(kExpectedDistanceAdjustTableSize == height);
+ fTable = new SkScalar[height];
+
+ SkAutoTArray<uint8_t> data((int)size);
+ SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
+
+ // find the inverse points where we cross 0.5
+ // binsearch might be better, but we only need to do this once on creation
+ for (int row = 0; row < height; ++row) {
+ uint8_t* rowPtr = data.get() + row*width;
+ for (int col = 0; col < width - 1; ++col) {
+ if (rowPtr[col] <= 127 && rowPtr[col + 1] >= 128) {
+ // compute point where a mask value will give us a result of 0.5
+ float interp = (127.5f - rowPtr[col]) / (rowPtr[col + 1] - rowPtr[col]);
+ float borderAlpha = (col + interp) / 255.f;
+
+ // compute t value for that alpha
+ // this is an approximate inverse for smoothstep()
+ float t = borderAlpha*(borderAlpha*(4.0f*borderAlpha - 6.0f) + 5.0f) / 3.0f;
+
+ // compute distance which gives us that t value
+ const float kDistanceFieldAAFactor = 0.65f; // should match SK_DistanceFieldAAFactor
+ float d = 2.0f*kDistanceFieldAAFactor*t - kDistanceFieldAAFactor;
+
+ fTable[row] = d;
+ break;
+ }
+ }
+ }
+}
diff --git a/src/gpu/text/GrDistanceFieldAdjustTable.h b/src/gpu/text/GrDistanceFieldAdjustTable.h
new file mode 100644
index 0000000000..f7d8bee089
--- /dev/null
+++ b/src/gpu/text/GrDistanceFieldAdjustTable.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrDistanceFieldAdjustTable_DEFINED
+#define GrDistanceFieldAdjustTable_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+
+// Distance field text needs this table to compute a value for use in the fragment shader.
+// Because the GrAtlasTextContext can go out of scope before the final flush, this needs to be
+// refcnted and malloced
+struct GrDistanceFieldAdjustTable : public SkNVRefCnt<GrDistanceFieldAdjustTable> {
+ GrDistanceFieldAdjustTable() { this->buildDistanceAdjustTable(); }
+ ~GrDistanceFieldAdjustTable() { delete[] fTable; }
+
+ const SkScalar& operator[] (int i) const {
+ return fTable[i];
+ }
+
+private:
+ void buildDistanceAdjustTable();
+
+ SkScalar* fTable;
+};
+
+#endif
diff --git a/src/gpu/text/GrFontScaler.cpp b/src/gpu/text/GrFontScaler.cpp
new file mode 100644
index 0000000000..c8412027c0
--- /dev/null
+++ b/src/gpu/text/GrFontScaler.cpp
@@ -0,0 +1,207 @@
+
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrFontScaler.h"
+#include "SkDescriptor.h"
+#include "SkDistanceFieldGen.h"
+#include "SkGlyphCache.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrFontScaler::GrFontScaler(SkGlyphCache* strike) {
+ fStrike = strike;
+ fKey = nullptr;
+}
+
+GrFontScaler::~GrFontScaler() {
+ SkSafeUnref(fKey);
+}
+
+GrMaskFormat GrFontScaler::getMaskFormat() const {
+ SkMask::Format format = fStrike->getMaskFormat();
+ switch (format) {
+ case SkMask::kBW_Format:
+ // fall through to kA8 -- we store BW glyphs in our 8-bit cache
+ case SkMask::kA8_Format:
+ return kA8_GrMaskFormat;
+ case SkMask::kLCD16_Format:
+ return kA565_GrMaskFormat;
+ case SkMask::kARGB32_Format:
+ return kARGB_GrMaskFormat;
+ default:
+ SkDEBUGFAIL("unsupported SkMask::Format");
+ return kA8_GrMaskFormat;
+ }
+}
+
+const GrFontDescKey* GrFontScaler::getKey() {
+ if (nullptr == fKey) {
+ fKey = new GrFontDescKey(fStrike->getDescriptor());
+ }
+ return fKey;
+}
+
+GrMaskFormat GrFontScaler::getPackedGlyphMaskFormat(const SkGlyph& glyph) const {
+ SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ switch (format) {
+ case SkMask::kBW_Format:
+ // fall through to kA8 -- we store BW glyphs in our 8-bit cache
+ case SkMask::kA8_Format:
+ return kA8_GrMaskFormat;
+ case SkMask::kLCD16_Format:
+ return kA565_GrMaskFormat;
+ case SkMask::kARGB32_Format:
+ return kARGB_GrMaskFormat;
+ default:
+ SkDEBUGFAIL("unsupported SkMask::Format");
+ return kA8_GrMaskFormat;
+ }
+}
+
+bool GrFontScaler::getPackedGlyphBounds(const SkGlyph& glyph, SkIRect* bounds) {
+#if 1
+ // crbug:510931
+ // Retrieving the image from the cache can actually change the mask format.
+ fStrike->findImage(glyph);
+#endif
+ bounds->setXYWH(glyph.fLeft, glyph.fTop, glyph.fWidth, glyph.fHeight);
+
+ return true;
+}
+
+bool GrFontScaler::getPackedGlyphDFBounds(const SkGlyph& glyph, SkIRect* bounds) {
+#if 1
+ // crbug:510931
+ // Retrieving the image from the cache can actually change the mask format.
+ fStrike->findImage(glyph);
+#endif
+ bounds->setXYWH(glyph.fLeft, glyph.fTop, glyph.fWidth, glyph.fHeight);
+ bounds->outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
+
+ return true;
+}
+
+namespace {
+// expands each bit in a bitmask to 0 or ~0 of type INT_TYPE. Used to expand a BW glyph mask to
+// A8, RGB565, or RGBA8888.
+template <typename INT_TYPE>
+void expand_bits(INT_TYPE* dst,
+ const uint8_t* src,
+ int width,
+ int height,
+ int dstRowBytes,
+ int srcRowBytes) {
+ for (int i = 0; i < height; ++i) {
+ int rowWritesLeft = width;
+ const uint8_t* s = src;
+ INT_TYPE* d = dst;
+ while (rowWritesLeft > 0) {
+ unsigned mask = *s++;
+ for (int i = 7; i >= 0 && rowWritesLeft; --i, --rowWritesLeft) {
+ *d++ = (mask & (1 << i)) ? (INT_TYPE)(~0UL) : 0;
+ }
+ }
+ dst = reinterpret_cast<INT_TYPE*>(reinterpret_cast<intptr_t>(dst) + dstRowBytes);
+ src += srcRowBytes;
+ }
+}
+}
+
+bool GrFontScaler::getPackedGlyphImage(const SkGlyph& glyph, int width, int height, int dstRB,
+ GrMaskFormat expectedMaskFormat, void* dst) {
+ SkASSERT(glyph.fWidth == width);
+ SkASSERT(glyph.fHeight == height);
+ const void* src = fStrike->findImage(glyph);
+ if (nullptr == src) {
+ return false;
+ }
+
+ // crbug:510931
+ // Retrieving the image from the cache can actually change the mask format. This case is very
+ // uncommon so for now we just draw a clear box for these glyphs.
+ if (getPackedGlyphMaskFormat(glyph) != expectedMaskFormat) {
+ const int bpp = GrMaskFormatBytesPerPixel(expectedMaskFormat);
+ for (int y = 0; y < height; y++) {
+ sk_bzero(dst, width * bpp);
+ dst = (char*)dst + dstRB;
+ }
+ return true;
+ }
+
+ int srcRB = glyph.rowBytes();
+ // The windows font host sometimes has BW glyphs in a non-BW strike. So it is important here to
+ // check the glyph's format, not the strike's format, and to be able to convert to any of the
+ // GrMaskFormats.
+ if (SkMask::kBW_Format == glyph.fMaskFormat) {
+ // expand bits to our mask type
+ const uint8_t* bits = reinterpret_cast<const uint8_t*>(src);
+ switch (expectedMaskFormat) {
+ case kA8_GrMaskFormat:{
+ uint8_t* bytes = reinterpret_cast<uint8_t*>(dst);
+ expand_bits(bytes, bits, width, height, dstRB, srcRB);
+ break;
+ }
+ case kA565_GrMaskFormat: {
+ uint16_t* rgb565 = reinterpret_cast<uint16_t*>(dst);
+ expand_bits(rgb565, bits, width, height, dstRB, srcRB);
+ break;
+ }
+ default:
+ SkFAIL("Invalid GrMaskFormat");
+ }
+ } else if (srcRB == dstRB) {
+ memcpy(dst, src, dstRB * height);
+ } else {
+ const int bbp = GrMaskFormatBytesPerPixel(expectedMaskFormat);
+ for (int y = 0; y < height; y++) {
+ memcpy(dst, src, width * bbp);
+ src = (const char*)src + srcRB;
+ dst = (char*)dst + dstRB;
+ }
+ }
+ return true;
+}
+
+bool GrFontScaler::getPackedGlyphDFImage(const SkGlyph& glyph, int width, int height, void* dst) {
+ SkASSERT(glyph.fWidth + 2*SK_DistanceFieldPad == width);
+ SkASSERT(glyph.fHeight + 2*SK_DistanceFieldPad == height);
+ const void* image = fStrike->findImage(glyph);
+ if (nullptr == image) {
+ return false;
+ }
+ // now generate the distance field
+ SkASSERT(dst);
+ SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ if (SkMask::kA8_Format == maskFormat) {
+ // make the distance field from the image
+ SkGenerateDistanceFieldFromA8Image((unsigned char*)dst,
+ (unsigned char*)image,
+ glyph.fWidth, glyph.fHeight,
+ glyph.rowBytes());
+ } else if (SkMask::kBW_Format == maskFormat) {
+ // make the distance field from the image
+ SkGenerateDistanceFieldFromBWImage((unsigned char*)dst,
+ (unsigned char*)image,
+ glyph.fWidth, glyph.fHeight,
+ glyph.rowBytes());
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+const SkPath* GrFontScaler::getGlyphPath(const SkGlyph& glyph) {
+ return fStrike->findPath(glyph);
+}
+
+const SkGlyph& GrFontScaler::grToSkGlyph(GrGlyph::PackedID id) {
+ return fStrike->getGlyphIDMetrics(GrGlyph::UnpackID(id),
+ GrGlyph::UnpackFixedX(id),
+ GrGlyph::UnpackFixedY(id));
+}
diff --git a/src/gpu/text/GrFontScaler.h b/src/gpu/text/GrFontScaler.h
new file mode 100644
index 0000000000..e42c7a1da0
--- /dev/null
+++ b/src/gpu/text/GrFontScaler.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrFontScaler_DEFINED
+#define GrFontScaler_DEFINED
+
+#include "GrGlyph.h"
+#include "GrTypes.h"
+
+#include "SkDescriptor.h"
+
+class SkGlyph;
+class SkPath;
+
+/*
+ * Wrapper class to turn a font cache descriptor into a key
+ * for GrFontScaler-related lookups
+ */
+class GrFontDescKey : public SkRefCnt {
+public:
+ explicit GrFontDescKey(const SkDescriptor& desc) : fDesc(desc), fHash(desc.getChecksum()) {}
+
+ uint32_t getHash() const { return fHash; }
+
+ bool operator==(const GrFontDescKey& rh) const {
+ return fHash == rh.fHash && fDesc.getDesc()->equals(*rh.fDesc.getDesc());
+ }
+
+private:
+ SkAutoDescriptor fDesc;
+ uint32_t fHash;
+
+ typedef SkRefCnt INHERITED;
+};
+
+/*
+ * This is Gr's interface to the host platform's font scaler.
+ *
+ * The client is responsible for instantiating this. The instance is created
+ * for a specific font+size+matrix.
+ */
+class GrFontScaler : public SkRefCnt {
+public:
+ explicit GrFontScaler(SkGlyphCache* strike);
+ virtual ~GrFontScaler();
+
+ const GrFontDescKey* getKey();
+ GrMaskFormat getMaskFormat() const;
+ GrMaskFormat getPackedGlyphMaskFormat(const SkGlyph&) const;
+ bool getPackedGlyphBounds(const SkGlyph&, SkIRect* bounds);
+ bool getPackedGlyphImage(const SkGlyph&, int width, int height, int rowBytes,
+ GrMaskFormat expectedMaskFormat, void* image);
+ bool getPackedGlyphDFBounds(const SkGlyph&, SkIRect* bounds);
+ bool getPackedGlyphDFImage(const SkGlyph&, int width, int height, void* image);
+ const SkPath* getGlyphPath(const SkGlyph&);
+ const SkGlyph& grToSkGlyph(GrGlyph::PackedID);
+
+private:
+ SkGlyphCache* fStrike;
+ GrFontDescKey* fKey;
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/src/gpu/text/GrStencilAndCoverTextContext.cpp b/src/gpu/text/GrStencilAndCoverTextContext.cpp
new file mode 100644
index 0000000000..d28f1a803a
--- /dev/null
+++ b/src/gpu/text/GrStencilAndCoverTextContext.cpp
@@ -0,0 +1,616 @@
+/*
+ * 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 "GrStencilAndCoverTextContext.h"
+#include "GrAtlasTextContext.h"
+#include "GrDrawContext.h"
+#include "GrDrawTarget.h"
+#include "GrPath.h"
+#include "GrPathRange.h"
+#include "GrResourceProvider.h"
+#include "SkAutoKern.h"
+#include "SkDraw.h"
+#include "SkDrawProcs.h"
+#include "SkGlyphCache.h"
+#include "SkGpuDevice.h"
+#include "SkGrPriv.h"
+#include "SkPath.h"
+#include "SkTextBlobRunIterator.h"
+#include "SkTextMapStateProc.h"
+#include "SkTextFormatParams.h"
+
+#include "batches/GrDrawPathBatch.h"
+
+template<typename Key, typename Val> static void delete_hash_map_entry(const Key&, Val* val) {
+ SkASSERT(*val);
+ delete *val;
+}
+
+template<typename T> static void delete_hash_table_entry(T* val) {
+ SkASSERT(*val);
+ delete *val;
+}
+
+GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(GrContext* context,
+ const SkSurfaceProps& surfaceProps)
+ : INHERITED(context, surfaceProps),
+ fCacheSize(0) {
+}
+
+GrStencilAndCoverTextContext*
+GrStencilAndCoverTextContext::Create(GrContext* context, const SkSurfaceProps& surfaceProps) {
+ GrStencilAndCoverTextContext* textContext =
+ new GrStencilAndCoverTextContext(context, surfaceProps);
+ textContext->fFallbackTextContext = GrAtlasTextContext::Create(context, surfaceProps);
+
+ return textContext;
+}
+
+GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
+ fBlobIdCache.foreach(delete_hash_map_entry<uint32_t, TextBlob*>);
+ fBlobKeyCache.foreach(delete_hash_table_entry<TextBlob*>);
+}
+
+bool GrStencilAndCoverTextContext::internalCanDraw(const SkPaint& skPaint) {
+ if (skPaint.getRasterizer()) {
+ return false;
+ }
+ if (skPaint.getMaskFilter()) {
+ return false;
+ }
+ if (SkPathEffect* pe = skPaint.getPathEffect()) {
+ if (pe->asADash(nullptr) != SkPathEffect::kDash_DashType) {
+ return false;
+ }
+ }
+ // No hairlines. They would require new paths with customized strokes for every new draw matrix.
+ return SkPaint::kStroke_Style != skPaint.getStyle() || 0 != skPaint.getStrokeWidth();
+}
+
+void GrStencilAndCoverTextContext::onDrawText(GrDrawContext* dc,
+ const GrClip& clip,
+ const GrPaint& paint,
+ const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char text[],
+ size_t byteLength,
+ SkScalar x, SkScalar y,
+ const SkIRect& clipBounds) {
+ TextRun run(skPaint);
+ GrPipelineBuilder pipelineBuilder(paint, dc->accessRenderTarget(), clip);
+ run.setText(text, byteLength, x, y);
+ run.draw(fContext, dc, &pipelineBuilder, paint.getColor(), viewMatrix, 0, 0, clipBounds,
+ fFallbackTextContext, skPaint);
+}
+
+void GrStencilAndCoverTextContext::onDrawPosText(GrDrawContext* dc,
+ const GrClip& clip,
+ const GrPaint& paint,
+ const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char text[],
+ size_t byteLength,
+ const SkScalar pos[],
+ int scalarsPerPosition,
+ const SkPoint& offset,
+ const SkIRect& clipBounds) {
+ TextRun run(skPaint);
+ GrPipelineBuilder pipelineBuilder(paint, dc->accessRenderTarget(), clip);
+ run.setPosText(text, byteLength, pos, scalarsPerPosition, offset);
+ run.draw(fContext, dc, &pipelineBuilder, paint.getColor(), viewMatrix, 0, 0, clipBounds,
+ fFallbackTextContext, skPaint);
+}
+
+void GrStencilAndCoverTextContext::drawTextBlob(GrDrawContext* dc,
+ const GrClip& clip, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const SkTextBlob* skBlob, SkScalar x, SkScalar y,
+ SkDrawFilter* drawFilter,
+ const SkIRect& clipBounds) {
+ if (!this->internalCanDraw(skPaint)) {
+ fFallbackTextContext->drawTextBlob(dc, clip, skPaint, viewMatrix, skBlob, x, y,
+ drawFilter, clipBounds);
+ return;
+ }
+
+ if (drawFilter || skPaint.getPathEffect()) {
+ // This draw can't be cached.
+ INHERITED::drawTextBlob(dc, clip, skPaint, viewMatrix, skBlob, x, y, drawFilter,
+ clipBounds);
+ return;
+ }
+
+ if (fContext->abandoned()) {
+ return;
+ }
+
+ GrPaint paint;
+ if (!SkPaintToGrPaint(fContext, skPaint, viewMatrix, &paint)) {
+ return;
+ }
+
+ const TextBlob& blob = this->findOrCreateTextBlob(skBlob, skPaint);
+ GrPipelineBuilder pipelineBuilder(paint, dc->accessRenderTarget(), clip);
+
+ TextBlob::Iter iter(blob);
+ for (TextRun* run = iter.get(); run; run = iter.next()) {
+ run->draw(fContext, dc, &pipelineBuilder, paint.getColor(), viewMatrix, x, y, clipBounds,
+ fFallbackTextContext, skPaint);
+ run->releaseGlyphCache();
+ }
+}
+
+const GrStencilAndCoverTextContext::TextBlob&
+GrStencilAndCoverTextContext::findOrCreateTextBlob(const SkTextBlob* skBlob,
+ const SkPaint& skPaint) {
+ // The font-related parameters are baked into the text blob and will override this skPaint, so
+ // the only remaining properties that can affect a TextBlob are the ones related to stroke.
+ if (SkPaint::kFill_Style == skPaint.getStyle()) { // Fast path.
+ if (TextBlob** found = fBlobIdCache.find(skBlob->uniqueID())) {
+ fLRUList.remove(*found);
+ fLRUList.addToTail(*found);
+ return **found;
+ }
+ TextBlob* blob = new TextBlob(skBlob->uniqueID(), skBlob, skPaint);
+ this->purgeToFit(*blob);
+ fBlobIdCache.set(skBlob->uniqueID(), blob);
+ fLRUList.addToTail(blob);
+ fCacheSize += blob->cpuMemorySize();
+ return *blob;
+ } else {
+ GrStrokeInfo stroke(skPaint);
+ SkSTArray<4, uint32_t, true> key;
+ key.reset(1 + stroke.computeUniqueKeyFragmentData32Cnt());
+ key[0] = skBlob->uniqueID();
+ stroke.asUniqueKeyFragment(&key[1]);
+ if (TextBlob** found = fBlobKeyCache.find(key)) {
+ fLRUList.remove(*found);
+ fLRUList.addToTail(*found);
+ return **found;
+ }
+ TextBlob* blob = new TextBlob(key, skBlob, skPaint);
+ this->purgeToFit(*blob);
+ fBlobKeyCache.set(blob);
+ fLRUList.addToTail(blob);
+ fCacheSize += blob->cpuMemorySize();
+ return *blob;
+ }
+}
+
+void GrStencilAndCoverTextContext::purgeToFit(const TextBlob& blob) {
+ static const size_t maxCacheSize = 4 * 1024 * 1024; // Allow up to 4 MB for caching text blobs.
+
+ size_t maxSizeForNewBlob = maxCacheSize - blob.cpuMemorySize();
+ while (fCacheSize && fCacheSize > maxSizeForNewBlob) {
+ TextBlob* lru = fLRUList.head();
+ if (1 == lru->key().count()) {
+ // 1-length keys are unterstood to be the blob id.
+ fBlobIdCache.remove(lru->key()[0]);
+ } else {
+ fBlobKeyCache.remove(lru->key());
+ }
+ fLRUList.remove(lru);
+ fCacheSize -= lru->cpuMemorySize();
+ delete lru;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void GrStencilAndCoverTextContext::TextBlob::init(const SkTextBlob* skBlob,
+ const SkPaint& skPaint) {
+ fCpuMemorySize = sizeof(TextBlob);
+ SkPaint runPaint(skPaint);
+ for (SkTextBlobRunIterator iter(skBlob); !iter.done(); iter.next()) {
+ iter.applyFontToPaint(&runPaint); // No need to re-seed the paint.
+ TextRun* run = this->addToTail(runPaint);
+
+ const char* text = reinterpret_cast<const char*>(iter.glyphs());
+ size_t byteLength = sizeof(uint16_t) * iter.glyphCount();
+ const SkPoint& runOffset = iter.offset();
+
+ switch (iter.positioning()) {
+ case SkTextBlob::kDefault_Positioning:
+ run->setText(text, byteLength, runOffset.fX, runOffset.fY);
+ break;
+ case SkTextBlob::kHorizontal_Positioning:
+ run->setPosText(text, byteLength, iter.pos(), 1, SkPoint::Make(0, runOffset.fY));
+ break;
+ case SkTextBlob::kFull_Positioning:
+ run->setPosText(text, byteLength, iter.pos(), 2, SkPoint::Make(0, 0));
+ break;
+ }
+
+ fCpuMemorySize += run->computeSizeInCache();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GrStencilAndCoverTextContext::FallbackBlobBuilder {
+public:
+ FallbackBlobBuilder() : fBuffIdx(0), fCount(0) {}
+
+ bool isInitialized() const { return SkToBool(fBuilder); }
+
+ void init(const SkPaint& font, SkScalar textRatio);
+
+ void appendGlyph(uint16_t glyphId, const SkPoint& pos);
+
+ const SkTextBlob* buildIfNeeded(int* count);
+
+private:
+ enum { kWriteBufferSize = 1024 };
+
+ void flush();
+
+ SkAutoTDelete<SkTextBlobBuilder> fBuilder;
+ SkPaint fFont;
+ int fBuffIdx;
+ int fCount;
+ uint16_t fGlyphIds[kWriteBufferSize];
+ SkPoint fPositions[kWriteBufferSize];
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke)
+ : fStroke(fontAndStroke),
+ fFont(fontAndStroke),
+ fTotalGlyphCount(0),
+ fFallbackGlyphCount(0),
+ fDetachedGlyphCache(nullptr),
+ fLastDrawnGlyphsID(SK_InvalidUniqueID) {
+ SkASSERT(!fStroke.isHairlineStyle()); // Hairlines are not supported.
+
+ // Setting to "fill" ensures that no strokes get baked into font outlines. (We use the GPU path
+ // rendering API for stroking).
+ fFont.setStyle(SkPaint::kFill_Style);
+
+ if (fFont.isFakeBoldText() && SkStrokeRec::kStroke_Style != fStroke.getStyle()) {
+ // Instead of letting fake bold get baked into the glyph outlines, do it with GPU stroke.
+ SkScalar fakeBoldScale = SkScalarInterpFunc(fFont.getTextSize(),
+ kStdFakeBoldInterpKeys,
+ kStdFakeBoldInterpValues,
+ kStdFakeBoldInterpLength);
+ SkScalar extra = SkScalarMul(fFont.getTextSize(), fakeBoldScale);
+ fStroke.setStrokeStyle(fStroke.needToApply() ? fStroke.getWidth() + extra : extra,
+ true /*strokeAndFill*/);
+
+ fFont.setFakeBoldText(false);
+ }
+
+ if (!fFont.getPathEffect() && !fStroke.isDashed()) {
+ // We can draw the glyphs from canonically sized paths.
+ fTextRatio = fFont.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
+ fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fFont.getTextSize();
+
+ // Compensate for the glyphs being scaled by fTextRatio.
+ if (!fStroke.isFillStyle()) {
+ fStroke.setStrokeStyle(fStroke.getWidth() / fTextRatio,
+ SkStrokeRec::kStrokeAndFill_Style == fStroke.getStyle());
+ }
+
+ fFont.setLinearText(true);
+ fFont.setLCDRenderText(false);
+ fFont.setAutohinted(false);
+ fFont.setHinting(SkPaint::kNo_Hinting);
+ fFont.setSubpixelText(true);
+ fFont.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
+
+ fUsingRawGlyphPaths = SK_Scalar1 == fFont.getTextScaleX() &&
+ 0 == fFont.getTextSkewX() &&
+ !fFont.isFakeBoldText() &&
+ !fFont.isVerticalText();
+ } else {
+ fTextRatio = fTextInverseRatio = 1.0f;
+ fUsingRawGlyphPaths = false;
+ }
+
+ // Generate the key that will be used to cache the GPU glyph path objects.
+ if (fUsingRawGlyphPaths && fStroke.isFillStyle()) {
+ static const GrUniqueKey::Domain kRawFillPathGlyphDomain = GrUniqueKey::GenerateDomain();
+
+ const SkTypeface* typeface = fFont.getTypeface();
+ GrUniqueKey::Builder builder(&fGlyphPathsKey, kRawFillPathGlyphDomain, 1);
+ reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0;
+ } else {
+ static const GrUniqueKey::Domain kPathGlyphDomain = GrUniqueKey::GenerateDomain();
+
+ int strokeDataCount = fStroke.computeUniqueKeyFragmentData32Cnt();
+ if (fUsingRawGlyphPaths) {
+ const SkTypeface* typeface = fFont.getTypeface();
+ GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, 2 + strokeDataCount);
+ reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0;
+ reinterpret_cast<uint32_t&>(builder[1]) = strokeDataCount;
+ fStroke.asUniqueKeyFragment(&builder[2]);
+ } else {
+ SkGlyphCache* glyphCache = this->getGlyphCache();
+ const SkTypeface* typeface = glyphCache->getScalerContext()->getTypeface();
+ const SkDescriptor* desc = &glyphCache->getDescriptor();
+ int descDataCount = (desc->getLength() + 3) / 4;
+ GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain,
+ 2 + strokeDataCount + descDataCount);
+ reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0;
+ reinterpret_cast<uint32_t&>(builder[1]) = strokeDataCount | (descDataCount << 16);
+ fStroke.asUniqueKeyFragment(&builder[2]);
+ memcpy(&builder[2 + strokeDataCount], desc, desc->getLength());
+ }
+ }
+}
+
+GrStencilAndCoverTextContext::TextRun::~TextRun() {
+ this->releaseGlyphCache();
+}
+
+void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t byteLength,
+ SkScalar x, SkScalar y) {
+ SkASSERT(byteLength == 0 || text != nullptr);
+
+ SkGlyphCache* glyphCache = this->getGlyphCache();
+ SkDrawCacheProc glyphCacheProc = fFont.getDrawCacheProc();
+
+ fTotalGlyphCount = fFont.countText(text, byteLength);
+ fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType,
+ fTotalGlyphCount));
+
+ const char* stop = text + byteLength;
+
+ // Measure first if needed.
+ if (fFont.getTextAlign() != SkPaint::kLeft_Align) {
+ SkFixed stopX = 0;
+ SkFixed stopY = 0;
+
+ const char* textPtr = text;
+ while (textPtr < stop) {
+ // We don't need x, y here, since all subpixel variants will have the
+ // same advance.
+ const SkGlyph& glyph = glyphCacheProc(glyphCache, &textPtr, 0, 0);
+
+ stopX += glyph.fAdvanceX;
+ stopY += glyph.fAdvanceY;
+ }
+ SkASSERT(textPtr == stop);
+
+ SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
+ SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
+
+ if (fFont.getTextAlign() == SkPaint::kCenter_Align) {
+ alignX = SkScalarHalf(alignX);
+ alignY = SkScalarHalf(alignY);
+ }
+
+ x -= alignX;
+ y -= alignY;
+ }
+
+ SkAutoKern autokern;
+
+ SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
+
+ SkFixed fx = SkScalarToFixed(x);
+ SkFixed fy = SkScalarToFixed(y);
+ FallbackBlobBuilder fallback;
+ while (text < stop) {
+ const SkGlyph& glyph = glyphCacheProc(glyphCache, &text, 0, 0);
+ fx += SkFixedMul(autokern.adjust(glyph), fixedSizeRatio);
+ if (glyph.fWidth) {
+ this->appendGlyph(glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy)),
+ &fallback);
+ }
+
+ fx += SkFixedMul(glyph.fAdvanceX, fixedSizeRatio);
+ fy += SkFixedMul(glyph.fAdvanceY, fixedSizeRatio);
+ }
+
+ fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount));
+}
+
+void GrStencilAndCoverTextContext::TextRun::setPosText(const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset) {
+ SkASSERT(byteLength == 0 || text != nullptr);
+ SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
+
+ SkGlyphCache* glyphCache = this->getGlyphCache();
+ SkDrawCacheProc glyphCacheProc = fFont.getDrawCacheProc();
+
+ fTotalGlyphCount = fFont.countText(text, byteLength);
+ fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType,
+ fTotalGlyphCount));
+
+ const char* stop = text + byteLength;
+
+ SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);
+ SkTextAlignProc alignProc(fFont.getTextAlign());
+ FallbackBlobBuilder fallback;
+ while (text < stop) {
+ const SkGlyph& glyph = glyphCacheProc(glyphCache, &text, 0, 0);
+ if (glyph.fWidth) {
+ SkPoint tmsLoc;
+ tmsProc(pos, &tmsLoc);
+ SkPoint loc;
+ alignProc(tmsLoc, glyph, &loc);
+
+ this->appendGlyph(glyph, loc, &fallback);
+ }
+ pos += scalarsPerPosition;
+ }
+
+ fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount));
+}
+
+GrPathRange* GrStencilAndCoverTextContext::TextRun::createGlyphs(GrContext* ctx) const {
+ GrPathRange* glyphs = static_cast<GrPathRange*>(
+ ctx->resourceProvider()->findAndRefResourceByUniqueKey(fGlyphPathsKey));
+ if (nullptr == glyphs) {
+ if (fUsingRawGlyphPaths) {
+ glyphs = ctx->resourceProvider()->createGlyphs(fFont.getTypeface(), nullptr, fStroke);
+ } else {
+ SkGlyphCache* cache = this->getGlyphCache();
+ glyphs = ctx->resourceProvider()->createGlyphs(cache->getScalerContext()->getTypeface(),
+ &cache->getDescriptor(),
+ fStroke);
+ }
+ ctx->resourceProvider()->assignUniqueKeyToResource(fGlyphPathsKey, glyphs);
+ }
+ return glyphs;
+}
+
+inline void GrStencilAndCoverTextContext::TextRun::appendGlyph(const SkGlyph& glyph,
+ const SkPoint& pos,
+ FallbackBlobBuilder* fallback) {
+ // Stick the glyphs we can't draw into the fallback text blob.
+ if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
+ if (!fallback->isInitialized()) {
+ fallback->init(fFont, fTextRatio);
+ }
+ fallback->appendGlyph(glyph.getGlyphID(), pos);
+ } else {
+ fInstanceData->append(glyph.getGlyphID(), fTextInverseRatio * pos.x(),
+ fTextInverseRatio * pos.y());
+ }
+}
+
+void GrStencilAndCoverTextContext::TextRun::draw(GrContext* ctx,
+ GrDrawContext* dc,
+ GrPipelineBuilder* pipelineBuilder,
+ GrColor color,
+ const SkMatrix& viewMatrix,
+ SkScalar x, SkScalar y,
+ const SkIRect& clipBounds,
+ GrTextContext* fallbackTextContext,
+ const SkPaint& originalSkPaint) const {
+ SkASSERT(fInstanceData);
+ SkASSERT(dc->accessRenderTarget()->isStencilBufferMultisampled() || !fFont.isAntiAlias());
+
+ if (fInstanceData->count()) {
+ pipelineBuilder->setState(GrPipelineBuilder::kHWAntialias_Flag, fFont.isAntiAlias());
+
+ GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
+ kZero_StencilOp,
+ kKeep_StencilOp,
+ kNotEqual_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+ *pipelineBuilder->stencil() = kStencilPass;
+
+ SkAutoTUnref<GrPathRange> glyphs(this->createGlyphs(ctx));
+ if (fLastDrawnGlyphsID != glyphs->getUniqueID()) {
+ // Either this is the first draw or the glyphs object was purged since last draw.
+ glyphs->loadPathsIfNeeded(fInstanceData->indices(), fInstanceData->count());
+ fLastDrawnGlyphsID = glyphs->getUniqueID();
+ }
+
+ // Don't compute a bounding box. For dst copy texture, we'll opt instead for it to just copy
+ // the entire dst. Realistically this is a moot point, because any context that supports
+ // NV_path_rendering will also support NV_blend_equation_advanced.
+ // For clipping we'll just skip any optimizations based on the bounds. This does, however,
+ // hurt batching.
+ SkRect bounds = SkRect::MakeIWH(pipelineBuilder->getRenderTarget()->width(),
+ pipelineBuilder->getRenderTarget()->height());
+
+ SkAutoTUnref<GrDrawPathBatchBase> batch(
+ GrDrawPathRangeBatch::Create(viewMatrix, fTextRatio, fTextInverseRatio * x,
+ fTextInverseRatio * y, color,
+ GrPathRendering::kWinding_FillType, glyphs, fInstanceData,
+ bounds));
+
+ dc->drawPathBatch(*pipelineBuilder, batch);
+ }
+
+ if (fFallbackTextBlob) {
+ SkPaint fallbackSkPaint(originalSkPaint);
+ fStroke.applyToPaint(&fallbackSkPaint);
+ if (!fStroke.isFillStyle()) {
+ fallbackSkPaint.setStrokeWidth(fStroke.getWidth() * fTextRatio);
+ }
+
+ fallbackTextContext->drawTextBlob(dc, pipelineBuilder->clip(), fallbackSkPaint, viewMatrix,
+ fFallbackTextBlob, x, y, nullptr, clipBounds);
+ }
+}
+
+SkGlyphCache* GrStencilAndCoverTextContext::TextRun::getGlyphCache() const {
+ if (!fDetachedGlyphCache) {
+ fDetachedGlyphCache = fFont.detachCache(nullptr, nullptr, true /*ignoreGamma*/);
+ }
+ return fDetachedGlyphCache;
+}
+
+
+void GrStencilAndCoverTextContext::TextRun::releaseGlyphCache() const {
+ if (fDetachedGlyphCache) {
+ SkGlyphCache::AttachCache(fDetachedGlyphCache);
+ fDetachedGlyphCache = nullptr;
+ }
+}
+
+size_t GrStencilAndCoverTextContext::TextRun::computeSizeInCache() const {
+ size_t size = sizeof(TextRun) + fGlyphPathsKey.size();
+ // The instance data always reserves enough space for every glyph.
+ size += (fTotalGlyphCount + fFallbackGlyphCount) * (sizeof(uint16_t) + 2 * sizeof(float));
+ if (fInstanceData) {
+ size += sizeof(InstanceData);
+ }
+ if (fFallbackTextBlob) {
+ size += sizeof(SkTextBlob);
+ }
+ return size;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void GrStencilAndCoverTextContext::FallbackBlobBuilder::init(const SkPaint& font,
+ SkScalar textRatio) {
+ SkASSERT(!this->isInitialized());
+ fBuilder.reset(new SkTextBlobBuilder);
+ fFont = font;
+ fFont.setTextAlign(SkPaint::kLeft_Align); // The glyph positions will already account for align.
+ fFont.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ // No need for subpixel positioning with bitmap glyphs. TODO: revisit if non-bitmap color glyphs
+ // show up and https://code.google.com/p/skia/issues/detail?id=4408 gets resolved.
+ fFont.setSubpixelText(false);
+ fFont.setTextSize(fFont.getTextSize() * textRatio);
+ fBuffIdx = 0;
+}
+
+void GrStencilAndCoverTextContext::FallbackBlobBuilder::appendGlyph(uint16_t glyphId,
+ const SkPoint& pos) {
+ SkASSERT(this->isInitialized());
+ if (fBuffIdx >= kWriteBufferSize) {
+ this->flush();
+ }
+ fGlyphIds[fBuffIdx] = glyphId;
+ fPositions[fBuffIdx] = pos;
+ fBuffIdx++;
+ fCount++;
+}
+
+void GrStencilAndCoverTextContext::FallbackBlobBuilder::flush() {
+ SkASSERT(this->isInitialized());
+ SkASSERT(fBuffIdx <= kWriteBufferSize);
+ if (!fBuffIdx) {
+ return;
+ }
+ // This will automatically merge with previous runs since we use the same font.
+ const SkTextBlobBuilder::RunBuffer& buff = fBuilder->allocRunPos(fFont, fBuffIdx);
+ memcpy(buff.glyphs, fGlyphIds, fBuffIdx * sizeof(uint16_t));
+ memcpy(buff.pos, fPositions[0].asScalars(), fBuffIdx * 2 * sizeof(SkScalar));
+ fBuffIdx = 0;
+}
+
+const SkTextBlob* GrStencilAndCoverTextContext::FallbackBlobBuilder::buildIfNeeded(int *count) {
+ *count = fCount;
+ if (fCount) {
+ this->flush();
+ return fBuilder->build();
+ }
+ return nullptr;
+}
diff --git a/src/gpu/text/GrStencilAndCoverTextContext.h b/src/gpu/text/GrStencilAndCoverTextContext.h
new file mode 100644
index 0000000000..dab71e0578
--- /dev/null
+++ b/src/gpu/text/GrStencilAndCoverTextContext.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrStencilAndCoverTextContext_DEFINED
+#define GrStencilAndCoverTextContext_DEFINED
+
+#include "GrTextContext.h"
+#include "GrDrawTarget.h"
+#include "GrStrokeInfo.h"
+#include "SkTHash.h"
+#include "SkTInternalLList.h"
+#include "SkTLList.h"
+#include "batches/GrDrawPathBatch.h"
+
+class GrTextStrike;
+class GrPath;
+class SkSurfaceProps;
+
+/*
+ * This class implements text rendering using stencil and cover path rendering
+ * (by the means of GrDrawTarget::drawPath).
+ * This class exposes the functionality through GrTextContext interface.
+ */
+class GrStencilAndCoverTextContext : public GrTextContext {
+public:
+ static GrStencilAndCoverTextContext* Create(GrContext*, const SkSurfaceProps&);
+
+ virtual ~GrStencilAndCoverTextContext();
+
+private:
+ GrStencilAndCoverTextContext(GrContext*, const SkSurfaceProps&);
+
+ bool canDraw(const SkPaint& skPaint, const SkMatrix&) override {
+ return this->internalCanDraw(skPaint);
+ }
+
+ bool internalCanDraw(const SkPaint&);
+
+ void onDrawText(GrDrawContext*, const GrClip&, const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkIRect& clipBounds) override;
+ void onDrawPosText(GrDrawContext*,
+ const GrClip&, const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& clipBounds) override;
+ void drawTextBlob(GrDrawContext*, const GrClip&, const SkPaint&,
+ const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y,
+ SkDrawFilter*, const SkIRect& clipBounds) override;
+
+ class FallbackBlobBuilder;
+
+ class TextRun {
+ public:
+ TextRun(const SkPaint& fontAndStroke);
+ ~TextRun();
+
+ void setText(const char text[], size_t byteLength, SkScalar x, SkScalar y);
+
+ void setPosText(const char text[], size_t byteLength, const SkScalar pos[],
+ int scalarsPerPosition, const SkPoint& offset);
+
+ void draw(GrContext*, GrDrawContext*, GrPipelineBuilder*, GrColor, const SkMatrix&,
+ SkScalar x, SkScalar y, const SkIRect& clipBounds,
+ GrTextContext* fallbackTextContext, const SkPaint& originalSkPaint) const;
+
+ void releaseGlyphCache() const;
+
+ size_t computeSizeInCache() const;
+
+ private:
+ typedef GrDrawPathRangeBatch::InstanceData InstanceData;
+
+ SkGlyphCache* getGlyphCache() const;
+ GrPathRange* createGlyphs(GrContext*) const;
+ void appendGlyph(const SkGlyph&, const SkPoint&, FallbackBlobBuilder*);
+
+ GrStrokeInfo fStroke;
+ SkPaint fFont;
+ SkScalar fTextRatio;
+ float fTextInverseRatio;
+ bool fUsingRawGlyphPaths;
+ GrUniqueKey fGlyphPathsKey;
+ int fTotalGlyphCount;
+ SkAutoTUnref<InstanceData> fInstanceData;
+ int fFallbackGlyphCount;
+ SkAutoTUnref<const SkTextBlob> fFallbackTextBlob;
+ mutable SkGlyphCache* fDetachedGlyphCache;
+ mutable uint32_t fLastDrawnGlyphsID;
+ };
+
+ // Text blobs/caches.
+
+ class TextBlob : public SkTLList<TextRun, 1> {
+ public:
+ typedef SkTArray<uint32_t, true> Key;
+
+ static const Key& GetKey(const TextBlob* blob) { return blob->key(); }
+
+ static uint32_t Hash(const Key& key) {
+ SkASSERT(key.count() > 1); // 1-length keys should be using the blob-id hash map.
+ return SkChecksum::Murmur3(key.begin(), sizeof(uint32_t) * key.count());
+ }
+
+ TextBlob(uint32_t blobId, const SkTextBlob* skBlob, const SkPaint& skPaint)
+ : fKey(&blobId, 1) { this->init(skBlob, skPaint); }
+
+ TextBlob(const Key& key, const SkTextBlob* skBlob, const SkPaint& skPaint)
+ : fKey(key) {
+ // 1-length keys are unterstood to be the blob id and must use the other constructor.
+ SkASSERT(fKey.count() > 1);
+ this->init(skBlob, skPaint);
+ }
+
+ const Key& key() const { return fKey; }
+
+ size_t cpuMemorySize() const { return fCpuMemorySize; }
+
+ private:
+ void init(const SkTextBlob*, const SkPaint&);
+
+ const SkSTArray<1, uint32_t, true> fKey;
+ size_t fCpuMemorySize;
+
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(TextBlob);
+ };
+
+ const TextBlob& findOrCreateTextBlob(const SkTextBlob*, const SkPaint&);
+ void purgeToFit(const TextBlob&);
+
+ SkTHashMap<uint32_t, TextBlob*> fBlobIdCache;
+ SkTHashTable<TextBlob*, const TextBlob::Key&, TextBlob> fBlobKeyCache;
+ SkTInternalLList<TextBlob> fLRUList;
+ size_t fCacheSize;
+
+ typedef GrTextContext INHERITED;
+};
+
+#endif
diff --git a/src/gpu/text/GrTextBlobCache.cpp b/src/gpu/text/GrTextBlobCache.cpp
new file mode 100644
index 0000000000..f11b7c60cb
--- /dev/null
+++ b/src/gpu/text/GrTextBlobCache.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrTextBlobCache.h"
+
+static const int kVerticesPerGlyph = 4;
+
+GrTextBlobCache::~GrTextBlobCache() {
+ this->freeAll();
+}
+
+GrAtlasTextBlob* GrTextBlobCache::createBlob(int glyphCount, int runCount, size_t maxVASize) {
+ // We allocate size for the GrAtlasTextBlob itself, plus size for the vertices array,
+ // and size for the glyphIds array.
+ size_t verticesCount = glyphCount * kVerticesPerGlyph * maxVASize;
+ size_t size = sizeof(GrAtlasTextBlob) +
+ verticesCount +
+ glyphCount * sizeof(GrGlyph**) +
+ sizeof(GrAtlasTextBlob::Run) * runCount;
+
+ void* allocation = fPool.allocate(size);
+#ifdef CACHE_SANITY_CHECK
+ sk_bzero(allocation, size);
+#endif
+
+ GrAtlasTextBlob* cacheBlob = new (allocation) GrAtlasTextBlob;
+#ifdef CACHE_SANITY_CHECK
+ cacheBlob->fSize = size;
+#endif
+
+ // setup offsets for vertices / glyphs
+ cacheBlob->fVertices = sizeof(GrAtlasTextBlob) + reinterpret_cast<unsigned char*>(cacheBlob);
+ cacheBlob->fGlyphs = reinterpret_cast<GrGlyph**>(cacheBlob->fVertices + verticesCount);
+ cacheBlob->fRuns = reinterpret_cast<GrAtlasTextBlob::Run*>(cacheBlob->fGlyphs + glyphCount);
+
+ // Initialize runs
+ for (int i = 0; i < runCount; i++) {
+ new (&cacheBlob->fRuns[i]) GrAtlasTextBlob::Run;
+ }
+ cacheBlob->fRunCount = runCount;
+ cacheBlob->fPool = &fPool;
+ return cacheBlob;
+}
+
+void GrTextBlobCache::freeAll() {
+ SkTDynamicHash<GrAtlasTextBlob, GrAtlasTextBlob::Key>::Iter iter(&fCache);
+ while (!iter.done()) {
+ GrAtlasTextBlob* blob = &(*iter);
+ fBlobList.remove(blob);
+ blob->unref();
+ ++iter;
+ }
+ fCache.rewind();
+}
diff --git a/src/gpu/text/GrTextBlobCache.h b/src/gpu/text/GrTextBlobCache.h
new file mode 100644
index 0000000000..8eee9d13db
--- /dev/null
+++ b/src/gpu/text/GrTextBlobCache.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTextBlobCache_DEFINED
+#define GrTextBlobCache_DEFINED
+
+#include "GrAtlasTextContext.h"
+#include "SkTDynamicHash.h"
+#include "SkTextBlobRunIterator.h"
+
+class GrTextBlobCache {
+public:
+ /**
+ * The callback function used by the cache when it is still over budget after a purge. The
+ * passed in 'data' is the same 'data' handed to setOverbudgetCallback.
+ */
+ typedef void (*PFOverBudgetCB)(void* data);
+
+ GrTextBlobCache(PFOverBudgetCB cb, void* data)
+ : fPool(kPreAllocSize, kMinGrowthSize)
+ , fCallback(cb)
+ , fData(data)
+ , fBudget(kDefaultBudget) {
+ SkASSERT(cb && data);
+ }
+ ~GrTextBlobCache();
+
+ // creates an uncached blob
+ GrAtlasTextBlob* createBlob(int glyphCount, int runCount, size_t maxVASize);
+ GrAtlasTextBlob* createBlob(const SkTextBlob* blob, size_t maxVAStride) {
+ int glyphCount = 0;
+ int runCount = 0;
+ BlobGlyphCount(&glyphCount, &runCount, blob);
+ GrAtlasTextBlob* cacheBlob = this->createBlob(glyphCount, runCount, maxVAStride);
+ return cacheBlob;
+ }
+
+ static void SetupCacheBlobKey(GrAtlasTextBlob* cacheBlob,
+ const GrAtlasTextBlob::Key& key,
+ const SkMaskFilter::BlurRec& blurRec,
+ const SkPaint& paint) {
+ cacheBlob->fKey = key;
+ if (key.fHasBlur) {
+ cacheBlob->fBlurRec = blurRec;
+ }
+ if (key.fStyle != SkPaint::kFill_Style) {
+ cacheBlob->fStrokeInfo.fFrameWidth = paint.getStrokeWidth();
+ cacheBlob->fStrokeInfo.fMiterLimit = paint.getStrokeMiter();
+ cacheBlob->fStrokeInfo.fJoin = paint.getStrokeJoin();
+ }
+ }
+
+ GrAtlasTextBlob* createCachedBlob(const SkTextBlob* blob,
+ const GrAtlasTextBlob::Key& key,
+ const SkMaskFilter::BlurRec& blurRec,
+ const SkPaint& paint,
+ size_t maxVAStride) {
+ int glyphCount = 0;
+ int runCount = 0;
+ BlobGlyphCount(&glyphCount, &runCount, blob);
+ GrAtlasTextBlob* cacheBlob = this->createBlob(glyphCount, runCount, maxVAStride);
+ SetupCacheBlobKey(cacheBlob, key, blurRec, paint);
+ this->add(cacheBlob);
+ return cacheBlob;
+ }
+
+ GrAtlasTextBlob* find(const GrAtlasTextBlob::Key& key) {
+ return fCache.find(key);
+ }
+
+ void remove(GrAtlasTextBlob* blob) {
+ fCache.remove(blob->fKey);
+ fBlobList.remove(blob);
+ blob->unref();
+ }
+
+ void add(GrAtlasTextBlob* blob) {
+ fCache.add(blob);
+ fBlobList.addToHead(blob);
+
+ this->checkPurge(blob);
+ }
+
+ void makeMRU(GrAtlasTextBlob* blob) {
+ if (fBlobList.head() == blob) {
+ return;
+ }
+
+ fBlobList.remove(blob);
+ fBlobList.addToHead(blob);
+ }
+
+ void freeAll();
+
+ // TODO move to SkTextBlob
+ static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
+ SkTextBlobRunIterator itCounter(blob);
+ for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
+ *glyphCount += itCounter.glyphCount();
+ }
+ }
+
+ void setBudget(size_t budget) {
+ fBudget = budget;
+ this->checkPurge();
+ }
+
+private:
+ typedef SkTInternalLList<GrAtlasTextBlob> BitmapBlobList;
+
+ void checkPurge(GrAtlasTextBlob* blob = nullptr) {
+ // If we are overbudget, then unref until we are below budget again
+ if (fPool.size() > fBudget) {
+ BitmapBlobList::Iter iter;
+ iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
+ GrAtlasTextBlob* lruBlob = nullptr;
+ while (fPool.size() > fBudget && (lruBlob = iter.get()) && lruBlob != blob) {
+ fCache.remove(lruBlob->fKey);
+
+ // Backup the iterator before removing and unrefing the blob
+ iter.prev();
+ fBlobList.remove(lruBlob);
+ lruBlob->unref();
+ }
+
+ // If we break out of the loop with lruBlob == blob, then we haven't purged enough
+ // use the call back and try to free some more. If we are still overbudget after this,
+ // then this single textblob is over our budget
+ if (blob && lruBlob == blob) {
+ (*fCallback)(fData);
+ }
+
+#ifdef SPEW_BUDGET_MESSAGE
+ if (fPool.size() > fBudget) {
+ SkDebugf("Single textblob is larger than our whole budget");
+ }
+#endif
+ }
+ }
+
+ // Budget was chosen to be ~4 megabytes. The min alloc and pre alloc sizes in the pool are
+ // based off of the largest cached textblob I have seen in the skps(a couple of kilobytes).
+ static const int kPreAllocSize = 1 << 17;
+ static const int kMinGrowthSize = 1 << 17;
+ static const int kDefaultBudget = 1 << 22;
+ BitmapBlobList fBlobList;
+ SkTDynamicHash<GrAtlasTextBlob, GrAtlasTextBlob::Key> fCache;
+ GrMemoryPool fPool;
+ PFOverBudgetCB fCallback;
+ void* fData;
+ size_t fBudget;
+};
+
+#endif
diff --git a/src/gpu/text/GrTextContext.cpp b/src/gpu/text/GrTextContext.cpp
new file mode 100644
index 0000000000..8ff2523a73
--- /dev/null
+++ b/src/gpu/text/GrTextContext.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrTextContext.h"
+#include "GrContext.h"
+#include "GrFontScaler.h"
+#include "GrTextUtils.h"
+
+#include "SkDrawFilter.h"
+#include "SkGlyphCache.h"
+#include "SkGrPriv.h"
+#include "SkTextBlobRunIterator.h"
+
+GrTextContext::GrTextContext(GrContext* context, const SkSurfaceProps& surfaceProps)
+ : fFallbackTextContext(nullptr)
+ , fContext(context)
+ , fSurfaceProps(surfaceProps) {
+}
+
+GrTextContext::~GrTextContext() {
+ delete fFallbackTextContext;
+}
+
+void GrTextContext::drawText(GrDrawContext* dc,
+ const GrClip& clip, const GrPaint& paint,
+ const SkPaint& skPaint, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkIRect& clipBounds) {
+ if (fContext->abandoned()) {
+ return;
+ }
+
+ GrTextContext* textContext = this;
+ do {
+ if (textContext->canDraw(skPaint, viewMatrix)) {
+ textContext->onDrawText(dc, clip, paint, skPaint, viewMatrix,
+ text, byteLength, x, y, clipBounds);
+ return;
+ }
+ textContext = textContext->fFallbackTextContext;
+ } while (textContext);
+
+ // fall back to drawing as a path
+ GrTextUtils::DrawTextAsPath(fContext, dc, clip, skPaint, viewMatrix, text, byteLength, x, y,
+ clipBounds);
+}
+
+void GrTextContext::drawPosText(GrDrawContext* dc,
+ const GrClip& clip, const GrPaint& paint,
+ const SkPaint& skPaint, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& clipBounds) {
+ if (fContext->abandoned()) {
+ return;
+ }
+
+ GrTextContext* textContext = this;
+ do {
+ if (textContext->canDraw(skPaint, viewMatrix)) {
+ textContext->onDrawPosText(dc, clip, paint, skPaint, viewMatrix,
+ text, byteLength, pos,
+ scalarsPerPosition, offset, clipBounds);
+ return;
+ }
+ textContext = textContext->fFallbackTextContext;
+ } while (textContext);
+
+ // fall back to drawing as a path
+ GrTextUtils::DrawPosTextAsPath(fContext, dc, fSurfaceProps, clip, skPaint, viewMatrix, text,
+ byteLength, pos, scalarsPerPosition, offset, clipBounds);
+}
+
+bool GrTextContext::ShouldDisableLCD(const SkPaint& paint) {
+ if (!SkXfermode::AsMode(paint.getXfermode(), nullptr) ||
+ paint.getMaskFilter() ||
+ paint.getRasterizer() ||
+ paint.getPathEffect() ||
+ paint.isFakeBoldText() ||
+ paint.getStyle() != SkPaint::kFill_Style)
+ {
+ return true;
+ }
+ return false;
+}
+
+uint32_t GrTextContext::FilterTextFlags(const SkSurfaceProps& surfaceProps, const SkPaint& paint) {
+ uint32_t flags = paint.getFlags();
+
+ if (!paint.isLCDRenderText() || !paint.isAntiAlias()) {
+ return flags;
+ }
+
+ if (kUnknown_SkPixelGeometry == surfaceProps.pixelGeometry() || ShouldDisableLCD(paint)) {
+ flags &= ~SkPaint::kLCDRenderText_Flag;
+ flags |= SkPaint::kGenA8FromLCD_Flag;
+ }
+
+ return flags;
+}
+
+void GrTextContext::drawTextBlob(GrDrawContext* dc,
+ const GrClip& clip, const SkPaint& skPaint,
+ const SkMatrix& viewMatrix, const SkTextBlob* blob,
+ SkScalar x, SkScalar y,
+ SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
+ SkPaint runPaint = skPaint;
+
+ SkTextBlobRunIterator it(blob);
+ for (;!it.done(); it.next()) {
+ size_t textLen = it.glyphCount() * sizeof(uint16_t);
+ const SkPoint& offset = it.offset();
+ // applyFontToPaint() always overwrites the exact same attributes,
+ // so it is safe to not re-seed the paint for this reason.
+ it.applyFontToPaint(&runPaint);
+
+ if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
+ // A false return from filter() means we should abort the current draw.
+ runPaint = skPaint;
+ continue;
+ }
+
+ runPaint.setFlags(FilterTextFlags(fSurfaceProps, runPaint));
+
+ GrPaint grPaint;
+ if (!SkPaintToGrPaint(fContext, runPaint, viewMatrix, &grPaint)) {
+ return;
+ }
+
+ switch (it.positioning()) {
+ case SkTextBlob::kDefault_Positioning:
+ this->drawText(dc, clip, grPaint, runPaint, viewMatrix, (const char *)it.glyphs(),
+ textLen, x + offset.x(), y + offset.y(), clipBounds);
+ break;
+ case SkTextBlob::kHorizontal_Positioning:
+ this->drawPosText(dc, clip, grPaint, runPaint, viewMatrix, (const char*)it.glyphs(),
+ textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()), clipBounds);
+ break;
+ case SkTextBlob::kFull_Positioning:
+ this->drawPosText(dc, clip, grPaint, runPaint, viewMatrix, (const char*)it.glyphs(),
+ textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds);
+ break;
+ default:
+ SkFAIL("unhandled positioning mode");
+ }
+
+ if (drawFilter) {
+ // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
+ runPaint = skPaint;
+ }
+ }
+}
+
+static void GlyphCacheAuxProc(void* data) {
+ GrFontScaler* scaler = (GrFontScaler*)data;
+ SkSafeUnref(scaler);
+}
+
+GrFontScaler* GrTextContext::GetGrFontScaler(SkGlyphCache* cache) {
+ void* auxData;
+ GrFontScaler* scaler = nullptr;
+
+ if (cache->getAuxProcData(GlyphCacheAuxProc, &auxData)) {
+ scaler = (GrFontScaler*)auxData;
+ }
+ if (nullptr == scaler) {
+ scaler = new GrFontScaler(cache);
+ cache->setAuxProc(GlyphCacheAuxProc, scaler);
+ }
+
+ return scaler;
+}
diff --git a/src/gpu/text/GrTextContext.h b/src/gpu/text/GrTextContext.h
new file mode 100644
index 0000000000..206b34a403
--- /dev/null
+++ b/src/gpu/text/GrTextContext.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTextContext_DEFINED
+#define GrTextContext_DEFINED
+
+#include "GrClip.h"
+#include "GrGlyph.h"
+#include "GrPaint.h"
+#include "SkSurfaceProps.h"
+#include "SkPostConfig.h"
+
+class GrClip;
+class GrContext;
+class GrDrawContext;
+class GrFontScaler;
+class SkDrawFilter;
+class SkTextBlob;
+
+/*
+ * This class wraps the state for a single text render
+ */
+class GrTextContext {
+public:
+ virtual ~GrTextContext();
+
+ void drawText(GrDrawContext* dc,
+ const GrClip&, const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix, const char text[], size_t byteLength, SkScalar x,
+ SkScalar y, const SkIRect& clipBounds);
+ void drawPosText(GrDrawContext* dc,
+ const GrClip&, const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& clipBounds);
+ virtual void drawTextBlob(GrDrawContext* dc, const GrClip&, const SkPaint&,
+ const SkMatrix& viewMatrix, const SkTextBlob*,
+ SkScalar x, SkScalar y,
+ SkDrawFilter*, const SkIRect& clipBounds);
+
+ static bool ShouldDisableLCD(const SkPaint& paint);
+
+protected:
+ GrTextContext* fFallbackTextContext;
+ GrContext* fContext;
+ SkSurfaceProps fSurfaceProps;
+
+ GrTextContext(GrContext*, const SkSurfaceProps&);
+
+ virtual bool canDraw(const SkPaint&, const SkMatrix& viewMatrix) = 0;
+
+ virtual void onDrawText(GrDrawContext*, const GrClip&,
+ const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix, const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkIRect& clipBounds) = 0;
+ virtual void onDrawPosText(GrDrawContext*, const GrClip&,
+ const GrPaint&, const SkPaint&,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& clipBounds) = 0;
+
+ static GrFontScaler* GetGrFontScaler(SkGlyphCache* cache);
+ static uint32_t FilterTextFlags(const SkSurfaceProps& surfaceProps, const SkPaint& paint);
+
+ friend class GrAtlasTextBatch;
+ friend class GrAtlasTextBlob; // for FilterTextFlags
+ friend class GrTextUtils; // for some static functions
+};
+
+#endif
diff --git a/src/gpu/text/GrTextUtils.cpp b/src/gpu/text/GrTextUtils.cpp
new file mode 100644
index 0000000000..293ea4d891
--- /dev/null
+++ b/src/gpu/text/GrTextUtils.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrTextUtils.h"
+
+#include "GrAtlasTextBlob.h"
+#include "GrBatchFontCache.h"
+#include "GrBlurUtils.h"
+#include "GrContext.h"
+#include "GrDrawContext.h"
+#include "GrTextContext.h"
+#include "SkDrawProcs.h"
+#include "SkFindAndPlaceGlyph.h"
+#include "SkGlyphCache.h"
+#include "SkPaint.h"
+#include "SkRect.h"
+#include "SkTextMapStateProc.h"
+#include "SkTextToPathIter.h"
+
+void GrTextUtils::DrawBmpText(GrAtlasTextBlob* blob, int runIndex,
+ GrBatchFontCache* fontCache,
+ SkGlyphCache* cache, const SkPaint& skPaint,
+ GrColor color,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y) {
+ SkASSERT(byteLength == 0 || text != nullptr);
+
+ // nothing to draw
+ if (text == nullptr || byteLength == 0) {
+ return;
+ }
+
+ GrBatchTextStrike* currStrike = nullptr;
+
+ // Get GrFontScaler from cache
+ GrFontScaler* fontScaler = GrTextContext::GetGrFontScaler(cache);
+
+ SkFindAndPlaceGlyph::ProcessText(
+ skPaint.getTextEncoding(), text, byteLength,
+ {x, y}, viewMatrix, skPaint.getTextAlign(),
+ cache,
+ [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) {
+ position += rounding;
+ BmpAppendGlyph(
+ blob, runIndex, fontCache, &currStrike, glyph,
+ SkScalarFloorToInt(position.fX), SkScalarFloorToInt(position.fY),
+ color, fontScaler);
+ }
+ );
+}
+
+void GrTextUtils::DrawBmpPosText(GrAtlasTextBlob* blob, int runIndex,
+ GrBatchFontCache* fontCache,
+ SkGlyphCache* cache, const SkPaint& skPaint,
+ GrColor color,
+ const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset) {
+ SkASSERT(byteLength == 0 || text != nullptr);
+ SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
+
+ // nothing to draw
+ if (text == nullptr || byteLength == 0) {
+ return;
+ }
+
+ GrBatchTextStrike* currStrike = nullptr;
+
+ // Get GrFontScaler from cache
+ GrFontScaler* fontScaler = GrTextContext::GetGrFontScaler(cache);
+
+ SkFindAndPlaceGlyph::ProcessPosText(
+ skPaint.getTextEncoding(), text, byteLength,
+ offset, viewMatrix, pos, scalarsPerPosition,
+ skPaint.getTextAlign(), cache,
+ [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) {
+ position += rounding;
+ BmpAppendGlyph(
+ blob, runIndex, fontCache, &currStrike, glyph,
+ SkScalarFloorToInt(position.fX), SkScalarFloorToInt(position.fY),
+ color, fontScaler);
+ }
+ );
+}
+
+void GrTextUtils::BmpAppendGlyph(GrAtlasTextBlob* blob, int runIndex,
+ GrBatchFontCache* fontCache,
+ GrBatchTextStrike** strike, const SkGlyph& skGlyph,
+ int vx, int vy, GrColor color, GrFontScaler* scaler) {
+ if (!*strike) {
+ *strike = fontCache->getStrike(scaler);
+ }
+
+ GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(),
+ skGlyph.getSubXFixed(),
+ skGlyph.getSubYFixed(),
+ GrGlyph::kCoverage_MaskStyle);
+ GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, scaler);
+ if (!glyph) {
+ return;
+ }
+
+ int x = vx + glyph->fBounds.fLeft;
+ int y = vy + glyph->fBounds.fTop;
+
+ // keep them as ints until we've done the clip-test
+ int width = glyph->fBounds.width();
+ int height = glyph->fBounds.height();
+
+ SkRect r;
+ r.fLeft = SkIntToScalar(x);
+ r.fTop = SkIntToScalar(y);
+ r.fRight = r.fLeft + SkIntToScalar(width);
+ r.fBottom = r.fTop + SkIntToScalar(height);
+
+ blob->appendGlyph(runIndex, r, color, *strike, glyph, scaler, skGlyph,
+ SkIntToScalar(vx), SkIntToScalar(vy), 1.0f, false);
+}
+
+void GrTextUtils::DrawTextAsPath(GrContext* context, GrDrawContext* dc,
+ const GrClip& clip,
+ const SkPaint& skPaint, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength, SkScalar x, SkScalar y,
+ const SkIRect& clipBounds) {
+ SkTextToPathIter iter(text, byteLength, skPaint, true);
+
+ SkMatrix matrix;
+ matrix.setScale(iter.getPathScale(), iter.getPathScale());
+ matrix.postTranslate(x, y);
+
+ const SkPath* iterPath;
+ SkScalar xpos, prevXPos = 0;
+
+ while (iter.next(&iterPath, &xpos)) {
+ matrix.postTranslate(xpos - prevXPos, 0);
+ if (iterPath) {
+ const SkPaint& pnt = iter.getPaint();
+ GrBlurUtils::drawPathWithMaskFilter(context, dc, clip, *iterPath,
+ pnt, viewMatrix, &matrix, clipBounds, false);
+ }
+ prevXPos = xpos;
+ }
+}
+
+void GrTextUtils::DrawPosTextAsPath(GrContext* context,
+ GrDrawContext* dc,
+ const SkSurfaceProps& props,
+ const GrClip& clip,
+ const SkPaint& origPaint, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& clipBounds) {
+ // setup our std paint, in hopes of getting hits in the cache
+ SkPaint paint(origPaint);
+ SkScalar matrixScale = paint.setupForAsPaths();
+
+ SkMatrix matrix;
+ matrix.setScale(matrixScale, matrixScale);
+
+ // Temporarily jam in kFill, so we only ever ask for the raw outline from the cache.
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setPathEffect(nullptr);
+
+ SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
+ SkAutoGlyphCache autoCache(paint, &props, nullptr);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ const char* stop = text + byteLength;
+ SkTextAlignProc alignProc(paint.getTextAlign());
+ SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);
+
+ // Now restore the original settings, so we "draw" with whatever style/stroking.
+ paint.setStyle(origPaint.getStyle());
+ paint.setPathEffect(origPaint.getPathEffect());
+
+ while (text < stop) {
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+ if (glyph.fWidth) {
+ const SkPath* path = cache->findPath(glyph);
+ if (path) {
+ SkPoint tmsLoc;
+ tmsProc(pos, &tmsLoc);
+ SkPoint loc;
+ alignProc(tmsLoc, glyph, &loc);
+
+ matrix[SkMatrix::kMTransX] = loc.fX;
+ matrix[SkMatrix::kMTransY] = loc.fY;
+ GrBlurUtils::drawPathWithMaskFilter(context, dc, clip, *path, paint,
+ viewMatrix, &matrix, clipBounds, false);
+ }
+ }
+ pos += scalarsPerPosition;
+ }
+}
diff --git a/src/gpu/text/GrTextUtils.h b/src/gpu/text/GrTextUtils.h
new file mode 100644
index 0000000000..8f673afbed
--- /dev/null
+++ b/src/gpu/text/GrTextUtils.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTextUtils_DEFINED
+#define GrTextUtils_DEFINED
+
+#include "GrColor.h"
+#include "SkScalar.h"
+
+class GrAtlasTextBlob;
+class GrBatchFontCache;
+class GrBatchTextStrike;
+class GrClip;
+class GrContext;
+class GrDrawContext;
+class GrFontScaler;
+class SkGlyph;
+class SkMatrix;
+struct SkIRect;
+class SkPaint;
+struct SkPoint;
+class SkGlyphCache;
+class SkSurfaceProps;
+
+/*
+ * A class to house a bunch of common text utilities. This class should *ONLY* have static
+ * functions. It is not a namespace only because we wish to friend SkPaint
+ *
+ */
+class GrTextUtils {
+public:
+ // Functions for appending BMP text to GrAtlasTextBlob
+ static void DrawBmpText(GrAtlasTextBlob*, int runIndex,
+ GrBatchFontCache*, SkGlyphCache*, const SkPaint&,
+ GrColor, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y);
+
+ static void DrawBmpPosText(GrAtlasTextBlob*, int runIndex,
+ GrBatchFontCache*, SkGlyphCache*, const SkPaint&,
+ GrColor, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset);
+
+ // Functions for drawing text as paths
+ static void DrawTextAsPath(GrContext*, GrDrawContext*, const GrClip& clip,
+ const SkPaint& origPaint, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength, SkScalar x, SkScalar y,
+ const SkIRect& clipBounds);
+
+ static void DrawPosTextAsPath(GrContext* context,
+ GrDrawContext* dc,
+ const SkSurfaceProps& props,
+ const GrClip& clip,
+ const SkPaint& origPaint, const SkMatrix& viewMatrix,
+ const char text[], size_t byteLength,
+ const SkScalar pos[], int scalarsPerPosition,
+ const SkPoint& offset, const SkIRect& clipBounds);
+private:
+ static void BmpAppendGlyph(GrAtlasTextBlob*, int runIndex, GrBatchFontCache*,
+ GrBatchTextStrike**, const SkGlyph&, int left, int top,
+ GrColor color, GrFontScaler*);
+};
+
+#endif