Index: src/gpu/GrStencilAndCoverTextContext.cpp |
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp |
index f6ce261ab7a7235ad8a298a8e238fc6904a875cc..c1d9e9d32d8b8a9eff2ee1ded132f7a74eafa81d 100644 |
--- a/src/gpu/GrStencilAndCoverTextContext.cpp |
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp |
@@ -17,97 +17,11 @@ |
#include "SkGpuDevice.h" |
#include "SkPath.h" |
#include "SkTextMapStateProc.h" |
- |
-class GrStencilAndCoverTextContext::GlyphPathRange : public GrGpuResource { |
- static const int kMaxGlyphCount = 1 << 16; // Glyph IDs are uint16_t's |
- static const int kGlyphGroupSize = 16; // Glyphs get tracked in groups of 16 |
- |
-public: |
- static GlyphPathRange* Create(GrContext* context, |
- SkGlyphCache* cache, |
- const SkStrokeRec& stroke) { |
- static const GrCacheID::Domain gGlyphPathRangeDomain = GrCacheID::GenerateDomain(); |
- |
- GrCacheID::Key key; |
- key.fData32[0] = cache->getDescriptor().getChecksum(); |
- key.fData32[1] = cache->getScalerContext()->getTypeface()->uniqueID(); |
- key.fData64[1] = GrPath::ComputeStrokeKey(stroke); |
- |
- GrResourceKey resourceKey(GrCacheID(gGlyphPathRangeDomain, key), |
- GrPathRange::resourceType(), 0); |
- SkAutoTUnref<GlyphPathRange> glyphs( |
- static_cast<GlyphPathRange*>(context->findAndRefCachedResource(resourceKey))); |
- |
- if (NULL == glyphs || |
- !glyphs->fDesc->equals(cache->getDescriptor() /*checksum collision*/)) { |
- glyphs.reset(SkNEW_ARGS(GlyphPathRange, (context, cache->getDescriptor(), stroke))); |
- glyphs->registerWithCache(); |
- context->addResourceToCache(resourceKey, glyphs); |
- } |
- |
- return glyphs.detach(); |
- } |
- |
- const GrPathRange* pathRange() const { return fPathRange.get(); } |
- |
- void preloadGlyph(uint16_t glyphID, SkGlyphCache* cache) { |
- const uint16_t groupIndex = glyphID / kGlyphGroupSize; |
- const uint16_t groupByte = groupIndex >> 3; |
- const uint8_t groupBit = 1 << (groupIndex & 7); |
- |
- const bool hasGlyph = 0 != (fLoadedGlyphs[groupByte] & groupBit); |
- if (hasGlyph) { |
- return; |
- } |
- |
- // We track which glyphs are loaded in groups of kGlyphGroupSize. To |
- // mark a glyph loaded we need to load the entire group. |
- const uint16_t groupFirstID = groupIndex * kGlyphGroupSize; |
- const uint16_t groupLastID = groupFirstID + kGlyphGroupSize - 1; |
- SkPath skPath; |
- for (int id = groupFirstID; id <= groupLastID; ++id) { |
- const SkGlyph& skGlyph = cache->getGlyphIDMetrics(id); |
- if (const SkPath* skPath = cache->findPath(skGlyph)) { |
- fPathRange->initAt(id, *skPath); |
- } // GrGpu::drawPaths will silently ignore undefined paths. |
- } |
- |
- fLoadedGlyphs[groupByte] |= groupBit; |
- this->didChangeGpuMemorySize(); |
- } |
- |
- // GrGpuResource overrides |
- virtual size_t gpuMemorySize() const SK_OVERRIDE { return fPathRange->gpuMemorySize(); } |
- |
-private: |
- GlyphPathRange(GrContext* context, const SkDescriptor& desc, const SkStrokeRec& stroke) |
- : INHERITED(context->getGpu(), false) |
- , fDesc(desc.copy()) |
- // We reserve a range of kMaxGlyphCount paths because of fallbacks fonts. We |
- // can't know exactly how many glyphs we might need without preloading every |
- // fallback, which we don't want to do at this point. |
- , fPathRange(context->getGpu()->pathRendering()->createPathRange(kMaxGlyphCount, stroke)) { |
- memset(fLoadedGlyphs, 0, sizeof(fLoadedGlyphs)); |
- } |
- |
- ~GlyphPathRange() { |
- this->release(); |
- SkDescriptor::Free(fDesc); |
- } |
- |
- static const int kMaxGroupCount = (kMaxGlyphCount + (kGlyphGroupSize - 1)) / kGlyphGroupSize; |
- SkDescriptor* const fDesc; |
- uint8_t fLoadedGlyphs[(kMaxGroupCount + 7) >> 3]; // One bit per glyph group |
- SkAutoTUnref<GrPathRange> fPathRange; |
- |
- typedef GrGpuResource INHERITED; |
-}; |
- |
+#include "SkTextFormatParams.h" |
GrStencilAndCoverTextContext::GrStencilAndCoverTextContext( |
GrContext* context, const SkDeviceProperties& properties) |
: GrTextContext(context, properties) |
- , fStroke(SkStrokeRec::kFill_InitStyle) |
, fPendingGlyphCount(0) { |
} |
@@ -140,22 +54,18 @@ void GrStencilAndCoverTextContext::drawText(const GrPaint& paint, |
// will turn off the use of device-space glyphs when perspective transforms |
// are in use. |
- this->init(paint, skPaint, byteLength, kUseIfNeeded_DeviceSpaceGlyphsBehavior); |
+ this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode); |
- SkMatrix* glyphCacheTransform = NULL; |
// Transform our starting point. |
if (fNeedsDeviceSpaceGlyphs) { |
SkPoint loc; |
fContextInitialMatrix.mapXY(x, y, &loc); |
x = loc.fX; |
y = loc.fY; |
- glyphCacheTransform = &fContextInitialMatrix; |
} |
SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
- SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform); |
- fGlyphCache = autoCache.getCache(); |
- fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke); |
+ |
fTransformType = GrPathRendering::kTranslate_PathTransformType; |
const char* stop = text + byteLength; |
@@ -232,14 +142,10 @@ void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint, |
// same glyphs as what were measured. |
const float textTranslateY = (1 == scalarsPerPosition ? constY : 0); |
- this->init(paint, skPaint, byteLength, kDoNotUse_DeviceSpaceGlyphsBehavior, textTranslateY); |
+ this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode, textTranslateY); |
SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
- SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL); |
- fGlyphCache = autoCache.getCache(); |
- fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke); |
- |
const char* stop = text + byteLength; |
if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
@@ -308,76 +214,145 @@ bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) { |
return rec.getFormat() != SkMask::kARGB32_Format; |
} |
+static GrPathRange* get_gr_glyphs(GrContext* ctx, |
+ const SkTypeface* typeface, |
+ const SkDescriptor* desc, |
+ const SkStrokeRec& stroke) { |
+ static const GrCacheID::Domain gGlyphsDomain = GrCacheID::GenerateDomain(); |
+ |
+ GrCacheID::Key key; |
+ uint64_t* keyData = key.fData64; |
+ keyData[0] = desc ? desc->getChecksum() : 0; |
+ keyData[0] = (keyData[0] << 32) | (typeface ? typeface->uniqueID() : 0); |
+ keyData[1] = GrPath::ComputeStrokeKey(stroke); |
+ GrResourceKey resourceKey = GrResourceKey(GrCacheID(gGlyphsDomain, key), |
+ GrPathRange::resourceType(), 0); |
+ |
+ SkAutoTUnref<GrPathRange> glyphs( |
+ static_cast<GrPathRange*>(ctx->findAndRefCachedResource(resourceKey))); |
+ if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) { |
+ glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke)); |
+ ctx->addResourceToCache(resourceKey, glyphs); |
+ } |
+ |
+ return glyphs.detach(); |
+} |
+ |
void GrStencilAndCoverTextContext::init(const GrPaint& paint, |
const SkPaint& skPaint, |
size_t textByteLength, |
- DeviceSpaceGlyphsBehavior deviceSpaceGlyphsBehavior, |
+ RenderMode renderMode, |
SkScalar textTranslateY) { |
GrTextContext::init(paint, skPaint); |
fContextInitialMatrix = fContext->getMatrix(); |
- bool otherBackendsWillDrawAsPaths = |
+ const bool otherBackendsWillDrawAsPaths = |
SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix); |
- if (otherBackendsWillDrawAsPaths) { |
- // This is to reproduce SkDraw::drawText_asPaths glyph positions. |
- fSkPaint.setLinearText(true); |
- fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; |
- fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize(); |
- fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); |
- if (fSkPaint.getStyle() != SkPaint::kFill_Style) { |
- // Compensate the glyphs being scaled up by fTextRatio by scaling the |
- // stroke down. |
- fSkPaint.setStrokeWidth(fSkPaint.getStrokeWidth() / fTextRatio); |
- } |
- fNeedsDeviceSpaceGlyphs = false; |
- } else { |
- fTextRatio = fTextInverseRatio = 1.0f; |
- fNeedsDeviceSpaceGlyphs = |
- kUseIfNeeded_DeviceSpaceGlyphsBehavior == deviceSpaceGlyphsBehavior && |
- (fContextInitialMatrix.getType() & |
- (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0; |
+ fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths && |
+ kMaxAccuracy_RenderMode == renderMode && |
+ SkToBool(fContextInitialMatrix.getType() & |
+ (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)); |
+ |
+ if (fNeedsDeviceSpaceGlyphs) { |
// SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms. |
SkASSERT(!fContextInitialMatrix.hasPerspective()); |
- } |
+ SkASSERT(0 == textTranslateY); // TODO: Handle textTranslateY in device-space usecase. |
- fStroke = SkStrokeRec(fSkPaint); |
+ fTextRatio = fTextInverseRatio = 1.0f; |
- if (fNeedsDeviceSpaceGlyphs) { |
- SkASSERT(1.0f == fTextRatio); |
- SkASSERT(0.0f == textTranslateY); |
- fPaint.localCoordChangeInverse(fContextInitialMatrix); |
- fContext->setIdentityMatrix(); |
- |
- // The whole shape is baked into the glyph. Make NVPR just fill the |
- // baked shape. |
- fStroke.setStrokeStyle(-1, false); |
+ // Glyphs loaded by GPU path rendering have an inverted y-direction. |
+ SkMatrix m; |
+ m.setScale(1, -1); |
+ fContext->setMatrix(m); |
+ |
+ // Post-flip the initial matrix so we're left with just the flip after |
+ // the paint preConcats the inverse. |
+ m = fContextInitialMatrix; |
+ m.postScale(1, -1); |
+ fPaint.localCoordChangeInverse(m); |
+ |
+ // The whole shape (including stroke) will be baked into the glyph outlines. Make |
+ // NVPR just fill the baked shapes. |
+ fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false); |
+ fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(), |
+ &fGlyphCache->getDescriptor(), |
+ SkStrokeRec(SkStrokeRec::kFill_InitStyle)); |
} else { |
- if (1.0f != fTextRatio || 0.0f != textTranslateY) { |
- SkMatrix textMatrix; |
- textMatrix.setTranslate(0, textTranslateY); |
- textMatrix.preScale(fTextRatio, fTextRatio); |
- fPaint.localCoordChange(textMatrix); |
- fContext->concatMatrix(textMatrix); |
+ // Don't bake strokes into the glyph outlines. We will stroke the glyphs |
+ // using the GPU instead. This is the fast path. |
+ SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint); |
+ fSkPaint.setStyle(SkPaint::kFill_Style); |
+ |
+ if (gpuStroke.isHairlineStyle()) { |
+ // Approximate hairline stroke. |
+ SkScalar strokeWidth = SK_Scalar1 / |
+ (SkVector::Make(fContextInitialMatrix.getScaleX(), |
+ fContextInitialMatrix.getSkewY()).length()); |
+ gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/); |
+ |
+ } else if (fSkPaint.isFakeBoldText() && |
+#ifdef SK_USE_FREETYPE_EMBOLDEN |
+ kMaxPerformance_RenderMode == renderMode && |
+#endif |
+ SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) { |
+ |
+ // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke. |
+ SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(), |
+ kStdFakeBoldInterpKeys, |
+ kStdFakeBoldInterpValues, |
+ kStdFakeBoldInterpLength); |
+ SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale); |
+ gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra, |
+ true /*strokeAndFill*/); |
+ |
+ fSkPaint.setFakeBoldText(false); |
} |
- if (fSkPaint.getStrokeWidth() == 0.0f) { |
- if (fSkPaint.getStyle() == SkPaint::kStrokeAndFill_Style) { |
- fStroke.setStrokeStyle(-1, false); |
- } else if (fSkPaint.getStyle() == SkPaint::kStroke_Style) { |
- // Approximate hairline stroke. |
- const SkMatrix& ctm = fContext->getMatrix(); |
- SkScalar strokeWidth = SK_Scalar1 / |
- (SkVector::Make(ctm.getScaleX(), ctm.getSkewY()).length()); |
- fStroke.setStrokeStyle(strokeWidth, false); |
+ bool canUseRawPaths; |
+ |
+ if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) { |
+ // We can draw the glyphs from canonically sized paths. |
+ fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; |
+ fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize(); |
+ |
+ // Compensate for the glyphs being scaled by fTextRatio. |
+ if (!gpuStroke.isFillStyle()) { |
+ gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio, |
+ SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle()); |
} |
+ |
+ fSkPaint.setLinearText(true); |
+ fSkPaint.setLCDRenderText(false); |
+ fSkPaint.setAutohinted(false); |
+ fSkPaint.setHinting(SkPaint::kNo_Hinting); |
+ fSkPaint.setSubpixelText(true); |
+ fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); |
+ |
+ canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() && |
+ 0 == fSkPaint.getTextSkewX() && |
+ !fSkPaint.isFakeBoldText() && |
+ !fSkPaint.isVerticalText(); |
+ } else { |
+ fTextRatio = fTextInverseRatio = 1.0f; |
+ canUseRawPaths = false; |
} |
- // Make glyph cache produce paths geometry for fill. We will stroke them |
- // by passing fStroke to drawPath. This is the fast path. |
- fSkPaint.setStyle(SkPaint::kFill_Style); |
+ SkMatrix textMatrix; |
+ textMatrix.setTranslate(0, textTranslateY); |
+ // Glyphs loaded by GPU path rendering have an inverted y-direction. |
+ textMatrix.preScale(fTextRatio, -fTextRatio); |
+ fPaint.localCoordChange(textMatrix); |
+ fContext->concatMatrix(textMatrix); |
+ |
+ fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false); |
+ fGlyphs = canUseRawPaths ? |
+ get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) : |
+ get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(), |
+ &fGlyphCache->getDescriptor(), gpuStroke); |
} |
+ |
fStateRestore.set(fDrawTarget->drawState()); |
fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(), |
@@ -403,8 +378,6 @@ inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x) |
this->flush(); |
} |
- fGlyphs->preloadGlyph(glyphID, fGlyphCache); |
- |
fIndexBuffer[fPendingGlyphCount] = glyphID; |
fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x; |
@@ -418,11 +391,9 @@ inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, |
this->flush(); |
} |
- fGlyphs->preloadGlyph(glyphID, fGlyphCache); |
- |
fIndexBuffer[fPendingGlyphCount] = glyphID; |
fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x; |
- fTransformBuffer[2 * fPendingGlyphCount + 1] = fTextInverseRatio * y; |
+ fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y; |
++fPendingGlyphCount; |
} |
@@ -432,7 +403,7 @@ void GrStencilAndCoverTextContext::flush() { |
return; |
} |
- fDrawTarget->drawPaths(fGlyphs->pathRange(), fIndexBuffer, fPendingGlyphCount, |
+ fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount, |
fTransformBuffer, fTransformType, SkPath::kWinding_FillType); |
fPendingGlyphCount = 0; |
@@ -441,8 +412,10 @@ void GrStencilAndCoverTextContext::flush() { |
void GrStencilAndCoverTextContext::finish() { |
this->flush(); |
- SkSafeUnref(fGlyphs); |
+ fGlyphs->unref(); |
fGlyphs = NULL; |
+ |
+ SkGlyphCache::AttachCache(fGlyphCache); |
fGlyphCache = NULL; |
fDrawTarget->drawState()->stencil()->setDisabled(); |