Add support for rotating / scaling / translating gpu cached distance field textblobs
authorjoshualitt <joshualitt@chromium.org>
Tue, 21 Apr 2015 16:43:03 +0000 (09:43 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 21 Apr 2015 16:43:03 +0000 (09:43 -0700)
BUG=skia:

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

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

index b5e8693e8db2e44f2191528f02da09d035500529..8c162220f9e2ec98d31e50e2a8c83cd6c57cc976 100644 (file)
@@ -287,15 +287,14 @@ bool GrAtlasTextContext::MustRegenerateBlob(SkScalar* outTransX, SkScalar* outTr
         return true;
     }
 
-    // TODO distance fields can handle many of these conditions
-    if (blob.fViewMatrix.getScaleX() != viewMatrix.getScaleX() ||
-        blob.fViewMatrix.getScaleY() != viewMatrix.getScaleY() ||
-        blob.fViewMatrix.getSkewX() != viewMatrix.getSkewX() ||
-        blob.fViewMatrix.getSkewY() != viewMatrix.getSkewY()) {
-        return true;
-    }
-
     if (blob.hasBitmap()) {
+        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
@@ -328,9 +327,18 @@ bool GrAtlasTextContext::MustRegenerateBlob(SkScalar* outTransX, SkScalar* outTr
         (*outTransX) = transX;
         (*outTransY) = transY;
     } else {
-        // blob.hasDistanceField()
-        // TODO figure out the regen formula
-        return true;
+        SkASSERT(blob.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 = blob.fViewMatrix.getMaxScale();
+        SkScalar scaleAdjust = newMaxScale / oldMaxScale;
+        if (scaleAdjust < blob.fMaxMinScale || scaleAdjust > blob.fMinMaxScale) {
+            return true;
+        }
+
+        (*outTransX) = x - blob.fX;
+        (*outTransY) = y - blob.fY;
     }
 
     return false;
@@ -503,7 +511,7 @@ void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob,
             cacheBlob->setHasDistanceField();
             SkPaint dfPaint = runPaint;
             SkScalar textRatio;
-            this->initDistanceFieldPaint(&dfPaint, &textRatio, viewMatrix);
+            this->initDistanceFieldPaint(cacheBlob, &dfPaint, &textRatio, viewMatrix);
             Run& runIdx = cacheBlob->fRuns[run];
             PerSubRunInfo& subRun = runIdx.fSubRunInfo.back();
             subRun.fUseLCDText = runPaint.isLCDRenderText();
@@ -581,7 +589,9 @@ void GrAtlasTextContext::regenerateTextBlob(BitmapTextBlob* cacheBlob,
     }
 }
 
-inline void GrAtlasTextContext::initDistanceFieldPaint(SkPaint* skPaint, SkScalar* textRatio,
+inline void GrAtlasTextContext::initDistanceFieldPaint(BitmapTextBlob* blob,
+                                                       SkPaint* skPaint,
+                                                       SkScalar* textRatio,
                                                        const SkMatrix& viewMatrix) {
     // getMaxScale doesn't support perspective, so neither do we at the moment
     SkASSERT(!viewMatrix.hasPerspective());
@@ -595,17 +605,37 @@ inline void GrAtlasTextContext::initDistanceFieldPaint(SkPaint* skPaint, SkScala
         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 = kMediumDFFontLimit;
         *textRatio = textSize / kSmallDFFontSize;
         skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize));
     } else if (scaledTextSize <= kMediumDFFontLimit) {
+        dfMaskScaleFloor = kMediumDFFontLimit;
+        dfMaskScaleCeil = kLargeDFFontSize;
         *textRatio = textSize / kMediumDFFontSize;
         skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize));
     } else {
+        dfMaskScaleFloor = kLargeDFFontSize;
+        dfMaskScaleCeil = 2 * kLargeDFFontSize;
         *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)
+    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);
@@ -645,7 +675,7 @@ GrAtlasTextContext::setupDFBlob(int glyphCount, const SkPaint& origPaint,
     BitmapTextBlob* blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize);
 
     *dfPaint = origPaint;
-    this->initDistanceFieldPaint(dfPaint, textRatio, viewMatrix);
+    this->initDistanceFieldPaint(blob, dfPaint, textRatio, viewMatrix);
     blob->fViewMatrix = viewMatrix;
     Run& run = blob->fRuns[0];
     PerSubRunInfo& subRun = run.fSubRunInfo.back();
index 4c833b20ff74aa12c6a14b57c37e4eb071e44dfa..d294126799815284d35e135455263b6d440ccb78 100644 (file)
@@ -235,13 +235,22 @@ private:
         mutable SkScalar fTotalXError;
         mutable SkScalar fTotalYError;
 #endif
+        SkColor fPaintColor;
         SkScalar fX;
         SkScalar fY;
-        SkColor fPaintColor;
+
+        // 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;
 
-        BitmapTextBlob() : fTextType(0) {}
+        BitmapTextBlob()
+            : fMaxMinScale(SK_ScalarMax)
+            , fMinMaxScale(SK_ScalarMax)
+            , fTextType(0) {}
 
         static const Key& GetKey(const BitmapTextBlob& blob) {
             return blob.fKey;
@@ -357,7 +366,8 @@ private:
                             SkDrawFilter* drawFilter, const SkIRect& clipRect, GrRenderTarget*,
                             const GrClip&, const GrPaint&);
     inline static bool HasLCD(const SkTextBlob*);
-    inline void initDistanceFieldPaint(SkPaint*, SkScalar* textRatio, const SkMatrix&);
+    inline void initDistanceFieldPaint(BitmapTextBlob*, SkPaint*, SkScalar* textRatio,
+                                       const SkMatrix&);
 
     // 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