Avoid regenerating cached textblobs on integer scrolls
authorjoshualitt <joshualitt@chromium.org>
Mon, 13 Apr 2015 13:12:21 +0000 (06:12 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 13 Apr 2015 13:12:21 +0000 (06:12 -0700)
BUG=skia:

Review URL: https://codereview.chromium.org/1062863002

src/gpu/GrAtlasTextContext.cpp
src/gpu/GrAtlasTextContext.h
src/gpu/GrTextBlobCache.cpp
src/gpu/GrTextBlobCache.h

index 8c89f2b..3a1bc31 100644 (file)
@@ -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);
 }
index 8fa6bb5..7859c04 100644 (file)
@@ -123,8 +123,12 @@ private:
             int fVx;
             int fVy;
         };
-
+#ifdef SK_DEBUG
+        mutable SkScalar fTotalXError;
+        mutable SkScalar fTotalYError;
+#endif
         SkTArray<BigGlyph> fBigGlyphs;
+        GrColor fColor; // the original color on the paint
         SkMatrix fViewMatrix;
         SkScalar fX;
         SkScalar fY;
@@ -171,14 +175,15 @@ private:
                                 const SkMatrix& viewMatrix, const SkIRect& clipBounds, SkScalar x,
                                 SkScalar y);
     inline void flushRun(GrDrawTarget*, GrPipelineBuilder*, BitmapTextBlob*, int run, GrColor,
-                         uint8_t paintAlpha);
+                         uint8_t paintAlpha, SkScalar transX, SkScalar transY);
     inline void flushBigGlyphs(BitmapTextBlob* cacheBlob, GrRenderTarget* rt,
-                               const GrPaint& grPaint, const GrClip& clip);
+                               const GrPaint& grPaint, const GrClip& clip,
+                               SkScalar transX, SkScalar transY);
 
     // We have to flush SkTextBlobs differently from drawText / drawPosText
     void flush(GrDrawTarget*, const SkTextBlob*, BitmapTextBlob*, GrRenderTarget*, const SkPaint&,
                const GrPaint&, SkDrawFilter*, const GrClip&, const SkMatrix& viewMatrix,
-               const SkIRect& clipBounds, SkScalar x, SkScalar y);
+               const SkIRect& clipBounds, SkScalar x, SkScalar y, SkScalar transX, SkScalar transY);
     void flush(GrDrawTarget*, BitmapTextBlob*, GrRenderTarget*, const SkPaint&,
                const GrPaint&, const GrClip&, const SkMatrix& viewMatrix);
 
@@ -193,7 +198,8 @@ private:
 
     // sets up the descriptor on the blob and returns a detached cache.  Client must attach
     inline SkGlyphCache* setupCache(Run*, const SkPaint&, const SkMatrix& viewMatrix);
-    static inline bool MustRegenerateBlob(const BitmapTextBlob&, const SkPaint&,
+    static inline bool MustRegenerateBlob(SkScalar* outTransX, SkScalar* outTransY,
+                                          const BitmapTextBlob&, const SkPaint&,
                                           const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
     void regenerateTextBlob(BitmapTextBlob* bmp, const SkPaint& skPaint, const SkMatrix& viewMatrix,
                             const SkTextBlob* blob, SkScalar x, SkScalar y,
index 9141b17..a5ef07c 100644 (file)
@@ -41,5 +41,10 @@ GrAtlasTextContext::BitmapTextBlob* GrTextBlobCache::createBlob(int glyphCount,
     }
     cacheBlob->fRunCount = runCount;
     cacheBlob->fPool = &fPool;
+
+#ifdef SK_DEBUG
+    cacheBlob->fTotalXError = 0;
+    cacheBlob->fTotalYError = 0;
+#endif
     return cacheBlob;
 }
index 1b5e5b8..0d0320e 100644 (file)
@@ -32,6 +32,13 @@ public:
 
     // creates an uncached blob
     BitmapTextBlob* createBlob(int glyphCount, int runCount, size_t maxVASize);
+    BitmapTextBlob* createBlob(const SkTextBlob* blob, size_t maxVAStride) {
+        int glyphCount = 0;
+        int runCount = 0;
+        BlobGlyphCount(&glyphCount, &runCount, blob);
+        BitmapTextBlob* cacheBlob = this->createBlob(glyphCount, runCount, maxVAStride);
+        return cacheBlob;
+    }
 
     BitmapTextBlob* createCachedBlob(const SkTextBlob* blob, size_t maxVAStride) {
         int glyphCount = 0;