Index: src/gpu/GrAtlasTextContext.cpp |
diff --git a/src/gpu/GrAtlasTextContext.cpp b/src/gpu/GrAtlasTextContext.cpp |
index 8c89f2b53ef5d3f6d46fcae60051da768b2ddf91..3a1bc31fa41f8cce9f8032a4b0e6f8bedadb7303 100644 |
--- a/src/gpu/GrAtlasTextContext.cpp |
+++ b/src/gpu/GrAtlasTextContext.cpp |
@@ -91,12 +91,62 @@ bool GrAtlasTextContext::canDraw(const GrRenderTarget*, |
return !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix); |
} |
-bool GrAtlasTextContext::MustRegenerateBlob(const BitmapTextBlob& blob, const SkPaint& paint, |
+bool GrAtlasTextContext::MustRegenerateBlob(SkScalar* outTransX, SkScalar* outTransY, |
+ const BitmapTextBlob& blob, const SkPaint& paint, |
const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { |
- // We always regenerate blobs with patheffects or mask filters we could cache these |
- // TODO find some way to cache the maskfilter / patheffects on the textblob |
- return !blob.fViewMatrix.cheapEqualTo(viewMatrix) || blob.fX != x || blob.fY != y || |
- paint.getMaskFilter() || paint.getPathEffect() || paint.getStyle() != blob.fStyle; |
+ // Color can affect the mask |
+ // TODO we can adjust the color within specific gamma slots |
+ if (blob.fColor != paint.getColor()) { |
+ return true; |
+ } |
+ |
+ if (blob.fViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) { |
+ return true; |
+ } |
+ |
+ if (blob.fViewMatrix.hasPerspective() && !blob.fViewMatrix.cheapEqualTo(viewMatrix)) { |
+ return true; |
+ } |
+ |
+ if (blob.fViewMatrix.getScaleX() != viewMatrix.getScaleX() || |
+ blob.fViewMatrix.getScaleY() != viewMatrix.getScaleY() || |
+ blob.fViewMatrix.getSkewX() != viewMatrix.getSkewX() || |
+ blob.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 - blob.fX) + |
+ viewMatrix.getSkewX() * (y - blob.fY) - |
+ blob.fViewMatrix.getTranslateX(); |
+ SkScalar transY = viewMatrix.getTranslateY() + |
+ viewMatrix.getSkewY() * (x - blob.fX) + |
+ viewMatrix.getScaleY() * (y - blob.fY) - |
+ blob.fViewMatrix.getTranslateY(); |
+ if (SkScalarFraction(transX) > SK_ScalarNearlyZero || |
+ SkScalarFraction(transY) > SK_ScalarNearlyZero) { |
+ return true; |
+ } |
+ |
+#ifdef SK_DEBUG |
+ static const SkScalar kMinDiscernableTranslation = 0.0625; |
+ // As a safeguard when debugging, we store the total error across all translations and print if |
+ // the error becomes discernable. This is pretty unlikely to occur given the tight bounds above |
+ // on translation |
+ blob.fTotalXError += SkScalarAbs(SkScalarFraction(transX)); |
+ blob.fTotalYError += SkScalarAbs(SkScalarFraction(transY)); |
+ if (blob.fTotalXError > kMinDiscernableTranslation || |
+ blob.fTotalYError > kMinDiscernableTranslation) { |
+ SkDebugf("Exceeding error threshold for bitmap text translation"); |
+ } |
+#endif |
+ (*outTransX) = transX; |
+ (*outTransY) = transY; |
+ return false; |
} |
@@ -113,24 +163,47 @@ void GrAtlasTextContext::drawTextBlob(GrRenderTarget* rt, const GrClip& clip, |
const SkTextBlob* blob, SkScalar x, SkScalar y, |
SkDrawFilter* drawFilter, const SkIRect& clipBounds) { |
uint32_t uniqueID = blob->uniqueID(); |
- BitmapTextBlob* cacheBlob = fCache->find(uniqueID); |
+ SkAutoTUnref<BitmapTextBlob> cacheBlob; |
+ // TODO start caching these, mix bits into the key |
+ bool canCache = !(skPaint.getPathEffect() || |
+ skPaint.getMaskFilter() || |
+ skPaint.getColorFilter() || |
+ skPaint.getStyle() != SkPaint::kFill_Style || |
+ drawFilter); |
+ |
+ if (canCache) { |
+ cacheBlob.reset(SkSafeRef(fCache->find(uniqueID))); |
+ } |
+ |
SkIRect clipRect; |
clip.getConservativeBounds(rt->width(), rt->height(), &clipRect); |
+ SkScalar transX = 0.f; |
+ SkScalar transY = 0.f; |
+ |
if (cacheBlob) { |
- if (MustRegenerateBlob(*cacheBlob, skPaint, viewMatrix, x, y)) { |
+ if (MustRegenerateBlob(&transX, &transY, *cacheBlob, skPaint, 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 = fCache->createCachedBlob(blob, kGrayTextVASize); |
+ cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, kGrayTextVASize))); |
this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, |
clipRect); |
} else { |
+ // If we can reuse the blob, then make sure we update the blob's viewmatrix and x/y |
+ // offsets to reflect the results of any translations we may apply in generateGeometry |
+ cacheBlob->fViewMatrix = viewMatrix; |
+ cacheBlob->fX = x; |
+ cacheBlob->fY = y; |
fCache->makeMRU(cacheBlob); |
} |
} else { |
- cacheBlob = fCache->createCachedBlob(blob, kGrayTextVASize); |
+ if (canCache) { |
+ cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, kGrayTextVASize))); |
+ } else { |
+ cacheBlob.reset(fCache->createBlob(blob, kGrayTextVASize)); |
+ } |
this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, clipRect); |
} |
@@ -140,7 +213,7 @@ void GrAtlasTextContext::drawTextBlob(GrRenderTarget* rt, const GrClip& clip, |
SkPaint2GrPaintShader(fContext, rt, skPaint, viewMatrix, true, &grPaint); |
this->flush(fContext->getTextTarget(), blob, cacheBlob, rt, skPaint, grPaint, drawFilter, |
- clip, viewMatrix, clipBounds, x, y); |
+ clip, viewMatrix, clipBounds, x, y, transX, transY); |
} |
void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob, |
@@ -150,6 +223,7 @@ void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob, |
cacheBlob->fViewMatrix = viewMatrix; |
cacheBlob->fX = x; |
cacheBlob->fY = y; |
+ cacheBlob->fColor = skPaint.getColor(); |
cacheBlob->fStyle = skPaint.getStyle(); |
// Regenerate textblob |
@@ -185,7 +259,7 @@ void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob, |
newRun.fGlyphEndIndex = lastRun.fGlyphEndIndex; |
} |
- if (SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix)) { |
+ if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) { |
cacheBlob->fRuns[run].fDrawAsPaths = true; |
continue; |
} |
@@ -536,10 +610,17 @@ void GrAtlasTextContext::appendGlyph(BitmapTextBlob* blob, int runIndex, GrGlyph |
int width = glyph->fBounds.width(); |
int height = glyph->fBounds.height(); |
+#if 0 |
+ // Not checking the clip bounds might introduce a performance regression. However, its not |
+ // clear if this is still true today with the larger tiles we use in Chrome. For repositionable |
+ // blobs, we want to make sure we have all of the glyphs, so clipping them out is not ideal. |
+ // We could store the cliprect in the key, but then we'd lose the ability to do integer scrolls |
+ // TODO verify this |
// check if we clipped out |
if (clipRect.quickReject(x, y, x + width, y + height)) { |
return; |
} |
+#endif |
// If the glyph is too large we fall back to paths |
if (fCurrStrike->glyphTooLargeForAtlas(glyph)) { |
@@ -598,7 +679,6 @@ void GrAtlasTextContext::appendGlyph(BitmapTextBlob* blob, int runIndex, GrGlyph |
*colorPtr = color; |
} |
vertex += vertexStride; |
- |
// V1 |
position = reinterpret_cast<SkPoint*>(vertex); |
position->set(r.fLeft, r.fBottom); |
@@ -640,11 +720,15 @@ public: |
: fBlob(SkRef(geometry.fBlob.get())) |
, fRun(geometry.fRun) |
, fSubRun(geometry.fSubRun) |
- , fColor(geometry.fColor) {} |
+ , fColor(geometry.fColor) |
+ , fTransX(geometry.fTransX) |
+ , fTransY(geometry.fTransY) {} |
SkAutoTUnref<Blob> fBlob; |
int fRun; |
int fSubRun; |
GrColor fColor; |
+ SkScalar fTransX; |
+ SkScalar fTransY; |
}; |
static GrBatch* Create(const Geometry& geometry, GrMaskFormat maskFormat, |
@@ -764,6 +848,7 @@ public: |
uint64_t currentAtlasGen = fFontCache->atlasGeneration(fMaskFormat); |
bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen; |
bool regenerateColors = kA8_GrMaskFormat == fMaskFormat && run.fColor != args.fColor; |
+ bool regeneratePositions = args.fTransX != 0.f || args.fTransY != 0.f; |
int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex; |
// We regenerate both texture coords and colors in the blob itself, and update the |
@@ -774,7 +859,7 @@ public: |
// or coords as needed. One final note, if we have to break a run for an atlas eviction |
// then we can't really trust the atlas has all of the correct data. Atlas evictions |
// should be pretty rare, so we just always regenerate in those cases |
- if (regenerateTextureCoords || regenerateColors) { |
+ if (regenerateTextureCoords || regenerateColors || regeneratePositions) { |
// first regenerate texture coordinates / colors if need be |
const SkDescriptor* desc = NULL; |
SkGlyphCache* cache = NULL; |
@@ -828,9 +913,19 @@ public: |
this->regenerateColors(vertex, vertexStride, args.fColor); |
} |
+ if (regeneratePositions) { |
+ intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); |
+ vertex += info.fVertexStartIndex; |
+ vertex += vertexStride * glyphIdx * kVerticesPerGlyph; |
+ SkScalar transX = args.fTransX; |
+ SkScalar transY = args.fTransY; |
+ this->regeneratePositions(vertex, vertexStride, transX, transY); |
+ } |
instancesToFlush++; |
} |
+ // We my have changed the color so update it here |
+ run.fColor = args.fColor; |
if (regenerateTextureCoords) { |
SkGlyphCache::AttachCache(cache); |
info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration : |
@@ -910,6 +1005,16 @@ private: |
} |
} |
+ void regeneratePositions(intptr_t vertex, size_t vertexStride, SkScalar transX, |
+ SkScalar transY) { |
+ for (int i = 0; i < kVerticesPerGlyph; i++) { |
+ SkPoint* point = reinterpret_cast<SkPoint*>(vertex); |
+ point->fX += transX; |
+ point->fY += transY; |
+ vertex += vertexStride; |
+ } |
+ } |
+ |
void initDraw(GrBatchTarget* batchTarget, |
const GrGeometryProcessor* gp, |
const GrPipeline* pipeline) { |
@@ -1016,7 +1121,7 @@ void GrAtlasTextContext::flushRunAsPaths(const SkTextBlob::RunIterator& it, cons |
inline void GrAtlasTextContext::flushRun(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, |
BitmapTextBlob* cacheBlob, int run, GrColor color, |
- uint8_t paintAlpha) { |
+ uint8_t paintAlpha, SkScalar transX, SkScalar transY) { |
for (int subRun = 0; subRun < cacheBlob->fRuns[run].fSubRunInfo.count(); subRun++) { |
const PerSubRunInfo& info = cacheBlob->fRuns[run].fSubRunInfo[subRun]; |
int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex; |
@@ -1034,6 +1139,8 @@ inline void GrAtlasTextContext::flushRun(GrDrawTarget* target, GrPipelineBuilder |
geometry.fRun = run; |
geometry.fSubRun = subRun; |
geometry.fColor = subRunColor; |
+ geometry.fTransX = transX; |
+ geometry.fTransY = transY; |
SkAutoTUnref<GrBatch> batch(BitmapTextBatch::Create(geometry, format, glyphCount, |
fContext->getBatchFontCache())); |
@@ -1042,11 +1149,15 @@ inline void GrAtlasTextContext::flushRun(GrDrawTarget* target, GrPipelineBuilder |
} |
inline void GrAtlasTextContext::flushBigGlyphs(BitmapTextBlob* cacheBlob, GrRenderTarget* rt, |
- const GrPaint& grPaint, const GrClip& clip) { |
+ const GrPaint& grPaint, const GrClip& clip, |
+ SkScalar transX, SkScalar transY) { |
for (int i = 0; i < cacheBlob->fBigGlyphs.count(); i++) { |
- const BitmapTextBlob::BigGlyph& bigGlyph = cacheBlob->fBigGlyphs[i]; |
+ BitmapTextBlob::BigGlyph& bigGlyph = cacheBlob->fBigGlyphs[i]; |
+ bigGlyph.fVx += SkScalarTruncToInt(transX); |
+ bigGlyph.fVy += SkScalarTruncToInt(transY); |
SkMatrix translate; |
- translate.setTranslate(SkIntToScalar(bigGlyph.fVx), SkIntToScalar(bigGlyph.fVy)); |
+ translate.setTranslate(SkIntToScalar(bigGlyph.fVx), |
+ SkIntToScalar(bigGlyph.fVy)); |
SkPath tmpPath(bigGlyph.fPath); |
tmpPath.transform(translate); |
GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle); |
@@ -1064,8 +1175,8 @@ void GrAtlasTextContext::flush(GrDrawTarget* target, |
const GrClip& clip, |
const SkMatrix& viewMatrix, |
const SkIRect& clipBounds, |
- SkScalar x, |
- SkScalar y) { |
+ 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; |
@@ -1080,11 +1191,12 @@ void GrAtlasTextContext::flush(GrDrawTarget* target, |
this->flushRunAsPaths(it, skPaint, drawFilter, viewMatrix, clipBounds, x, y); |
continue; |
} |
- this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, paintAlpha); |
+ cacheBlob->fRuns[run].fVertexBounds.offset(transX, transY); |
+ this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, paintAlpha, transX, transY); |
} |
// Now flush big glyphs |
- this->flushBigGlyphs(cacheBlob, rt, grPaint, clip); |
+ this->flushBigGlyphs(cacheBlob, rt, grPaint, clip, transX, transY); |
} |
void GrAtlasTextContext::flush(GrDrawTarget* target, |
@@ -1100,9 +1212,9 @@ void GrAtlasTextContext::flush(GrDrawTarget* target, |
GrColor color = grPaint.getColor(); |
uint8_t paintAlpha = skPaint.getAlpha(); |
for (int run = 0; run < cacheBlob->fRunCount; run++) { |
- this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, paintAlpha); |
+ this->flushRun(target, &pipelineBuilder, cacheBlob, run, color, paintAlpha, 0, 0); |
} |
// Now flush big glyphs |
- this->flushBigGlyphs(cacheBlob, rt, grPaint, clip); |
+ this->flushBigGlyphs(cacheBlob, rt, grPaint, clip, 0, 0); |
} |