Move bound and isFinite into pathref

https://codereview.chromium.org/24350006/



git-svn-id: http://skia.googlecode.com/svn/trunk@11467 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 5120805..3b57699 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -126,7 +126,7 @@
         '<(skia_src_path)/core/SkPathHeap.cpp',
         '<(skia_src_path)/core/SkPathHeap.h',
         '<(skia_src_path)/core/SkPathMeasure.cpp',
-        '<(skia_src_path)/core/SkPathRef.h',
+        '<(skia_src_path)/core/SkPathRef.cpp',
         '<(skia_src_path)/core/SkPicture.cpp',
         '<(skia_src_path)/core/SkPictureFlat.cpp',
         '<(skia_src_path)/core/SkPictureFlat.h',
@@ -263,6 +263,7 @@
         '<(skia_include_path)/core/SkPath.h',
         '<(skia_include_path)/core/SkPathEffect.h',
         '<(skia_include_path)/core/SkPathMeasure.h',
+        '<(skia_include_path)/core/SkPathRef.h',
         '<(skia_include_path)/core/SkPicture.h',
         '<(skia_include_path)/core/SkPixelRef.h',
         '<(skia_include_path)/core/SkPoint.h',
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index cc4dfcb..a6674d9 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -12,6 +12,7 @@
 
 #include "SkInstCnt.h"
 #include "SkMatrix.h"
+#include "SkPathRef.h"
 #include "SkTDArray.h"
 #include "SkRefCnt.h"
 
@@ -27,7 +28,6 @@
 class SkWriter32;
 class SkAutoPathBoundsUpdate;
 class SkString;
-class SkPathRef;
 class SkRRect;
 
 /** \class SkPath
@@ -181,17 +181,18 @@
 
         @return true if the path is empty (contains no lines or curves)
     */
-    bool isEmpty() const;
+    bool isEmpty() const {
+        SkDEBUGCODE(this->validate();)
+        return 0 == fPathRef->countVerbs();
+    }
 
     /**
      *  Returns true if all of the points in this path are finite, meaning there
      *  are no infinities and no NaNs.
      */
     bool isFinite() const {
-        if (fBoundsIsDirty) {
-            this->computeBounds();
-        }
-        return SkToBool(fIsFinite);
+        SkDEBUGCODE(this->validate();)
+        return fPathRef->isFinite();
     }
 
     /** Test a line for zero length
@@ -281,10 +282,7 @@
         do not extend as far as their control points.
     */
     const SkRect& getBounds() const {
-        if (fBoundsIsDirty) {
-            this->computeBounds();
-        }
-        return fBounds;
+        return fPathRef->getBounds();
     }
 
     /** Calling this will, if the internal cache of the bounds is out of date,
@@ -598,7 +596,7 @@
      *  @param dir  The direction to wind the rectangle's contour. Cannot be
      *              kUnknown_Direction.
      */
-    void    addRect(const SkRect& rect, Direction dir = kCW_Direction);
+    void addRect(const SkRect& rect, Direction dir = kCW_Direction);
 
     /**
      *  Add a closed rectangle contour to the path
@@ -657,8 +655,8 @@
      *  @param dir  The direction to wind the rectangle's contour. Cannot be
      *              kUnknown_Direction.
      */
-    void    addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
-                         Direction dir = kCW_Direction);
+    void addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
+                      Direction dir = kCW_Direction);
 
     /**
      *  Add a closed round-rectangle contour to the path. Each corner receives
@@ -932,24 +930,27 @@
 
 private:
     enum SerializationOffsets {
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+        kNewFormat_SerializationShift = 28, // requires 1 bit
+#endif
         kDirection_SerializationShift = 26, // requires 2 bits
-        kIsFinite_SerializationShift = 25,  // requires 1 bit
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+        // rename to kUnused_SerializationShift
+        kOldIsFinite_SerializationShift = 25,    // 1 bit
+#endif
         kIsOval_SerializationShift = 24,    // requires 1 bit
-        kConvexity_SerializationShift = 16, // requires 2 bits
-        kFillType_SerializationShift = 8,   // requires 2 bits
+        kConvexity_SerializationShift = 16, // requires 8 bits
+        kFillType_SerializationShift = 8,   // requires 8 bits
         kSegmentMask_SerializationShift = 0 // requires 4 bits
     };
 
     SkAutoTUnref<SkPathRef> fPathRef;
 
-    mutable SkRect      fBounds;
     int                 fLastMoveToIndex;
     uint8_t             fFillType;
     uint8_t             fSegmentMask;
-    mutable uint8_t     fBoundsIsDirty;
     mutable uint8_t     fConvexity;
     mutable uint8_t     fDirection;
-    mutable SkBool8     fIsFinite;    // only meaningful if bounds are valid
     mutable SkBool8     fIsOval;
 #ifdef SK_BUILD_FOR_ANDROID
     uint32_t            fGenerationID;
@@ -968,9 +969,6 @@
      */
     void copyFields(const SkPath& that);
 
-    // called, if dirty, by getBounds()
-    void computeBounds() const;
-
     friend class Iter;
 
     friend class SkPathStroker;
@@ -1001,6 +999,23 @@
     bool isRectContour(bool allowPartial, int* currVerb, const SkPoint** pts,
                        bool* isClosed, Direction* direction) const;
 
+    /** Returns if the path can return a bound at no cost (true) or will have to
+        perform some computation (false).
+     */
+    bool hasComputedBounds() const {
+        SkDEBUGCODE(this->validate();)
+        return fPathRef->hasComputedBounds();
+    }
+
+
+    // 'rect' needs to be sorted
+    void setBounds(const SkRect& rect) {
+        fPathRef->setBounds(rect);
+    }
+
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+    friend class SkPathRef;     // just for SerializationOffsets
+#endif
     friend class SkAutoPathBoundsUpdate;
     friend class SkAutoDisableOvalCheck;
     friend class SkAutoDisableDirectionCheck;
diff --git a/src/core/SkPathRef.h b/include/core/SkPathRef.h
similarity index 76%
rename from src/core/SkPathRef.h
rename to include/core/SkPathRef.h
index b789de8..c834486 100644
--- a/src/core/SkPathRef.h
+++ b/include/core/SkPathRef.h
@@ -9,9 +9,18 @@
 #ifndef SkPathRef_DEFINED
 #define SkPathRef_DEFINED
 
+#include "SkMatrix.h"
+#include "SkPoint.h"
+#include "SkRect.h"
 #include "SkRefCnt.h"
+#include "SkTDArray.h"
 #include <stddef.h> // ptrdiff_t
 
+class SkRBuffer;
+class SkWBuffer;
+
+// TODO: refactor this header to move more of the implementation into the .cpp
+
 /**
  * Holds the path verbs and points. It is versioned by a generation ID. None of its public methods
  * modify the contents. To modify or append to the verbs/points wrap the SkPathRef in an
@@ -27,8 +36,6 @@
  * logical verb or the last verb in memory).
  */
 
-class SkPathRef;
-
 class SkPathRef : public ::SkRefCnt {
 public:
     SK_DECLARE_INST_COUNT(SkPathRef);
@@ -70,17 +77,9 @@
          * Adds the verb and allocates space for the number of points indicated by the verb. The
          * return value is a pointer to where the points for the verb should be written.
          */
-        SkPoint* growForVerb(SkPath::Verb verb) {
-            fPathRef->validate();
-            return fPathRef->growForVerb(verb);
-        }
+        SkPoint* growForVerb(int /*SkPath::Verb*/ verb);
 
-        SkPoint* growForConic(SkScalar w) {
-            fPathRef->validate();
-            SkPoint* pts = fPathRef->growForVerb(SkPath::kConic_Verb);
-            *fPathRef->fConicWeights.append() = w;
-            return pts;
-        }
+        SkPoint* growForConic(SkScalar w);
 
         /**
          * Allocates space for additional verbs and points and returns pointers to the new verbs and
@@ -129,6 +128,40 @@
     }
 
     /**
+     *  Returns true if all of the points in this path are finite, meaning there
+     *  are no infinities and no NaNs.
+     */
+    bool isFinite() const {
+        if (fBoundsIsDirty) {
+            this->computeBounds();
+        }
+        return SkToBool(fIsFinite);
+    }
+
+    bool hasComputedBounds() const {
+        return !fBoundsIsDirty;
+    }
+
+    /** Returns the bounds of the path's points. If the path contains 0 or 1
+        points, the bounds is set to (0,0,0,0), and isEmpty() will return true.
+        Note: this bounds may be larger than the actual shape, since curves
+        do not extend as far as their control points.
+    */
+    const SkRect& getBounds() const {        
+        if (fBoundsIsDirty) {
+            this->computeBounds();
+        }
+        return fBounds;
+    }
+
+    void setBounds(const SkRect& rect) {
+        SkASSERT(rect.fLeft <= rect.fRight && rect.fTop <= rect.fBottom);
+        fBounds = rect;
+        fBoundsIsDirty = false;
+        fIsFinite = fBounds.isFinite();
+    }
+
+    /**
      * Transforms a path ref by a matrix, allocating a new one only if necessary.
      */
     static void CreateTransformedCopy(SkAutoTUnref<SkPathRef>* dst,
@@ -143,36 +176,53 @@
             }
             return;
         }
+
         bool dstUnique = (*dst)->unique();
-        if (&src == *dst && dstUnique) {
-            matrix.mapPoints((*dst)->fPoints, (*dst)->fPointCnt);
-            return;
-        } else if (!dstUnique) {
+        if (!dstUnique) {
             dst->reset(SkNEW(SkPathRef));
+            (*dst)->resetToSize(src.fVerbCnt, src.fPointCnt, src.fConicWeights.count());
+            memcpy((*dst)->verbsMemWritable(), src.verbsMemBegin(), src.fVerbCnt * sizeof(uint8_t));
+            (*dst)->fConicWeights = src.fConicWeights;
         }
-        (*dst)->resetToSize(src.fVerbCnt, src.fPointCnt, src.fConicWeights.count());
-        memcpy((*dst)->verbsMemWritable(), src.verbsMemBegin(), src.fVerbCnt * sizeof(uint8_t));
+
+        // Need to check this here in case (&src == dst)
+        bool canXformBounds = !src.fBoundsIsDirty && matrix.rectStaysRect() && src.countPoints() > 1;
+
         matrix.mapPoints((*dst)->fPoints, src.points(), src.fPointCnt);
-        (*dst)->fConicWeights = src.fConicWeights;
+
+        /*
+         *  Here we optimize the bounds computation, by noting if the bounds are
+         *  already known, and if so, we just transform those as well and mark
+         *  them as "known", rather than force the transformed path to have to
+         *  recompute them.
+         *
+         *  Special gotchas if the path is effectively empty (<= 1 point) or
+         *  if it is non-finite. In those cases bounds need to stay empty,
+         *  regardless of the matrix.
+         */
+        if (canXformBounds) {
+            (*dst)->fBoundsIsDirty = false;
+            if (src.fIsFinite) {
+                matrix.mapRect(&(*dst)->fBounds, src.fBounds);
+                if (!((*dst)->fIsFinite = (*dst)->fBounds.isFinite())) {
+                    (*dst)->fBounds.setEmpty();
+                }
+            } else {
+                (*dst)->fIsFinite = false;
+                (*dst)->fBounds.setEmpty();
+            }
+        } else {
+            (*dst)->fBoundsIsDirty = true;
+        }
+
         (*dst)->validate();
     }
 
-    static SkPathRef* CreateFromBuffer(SkRBuffer* buffer) {
-        SkPathRef* ref = SkNEW(SkPathRef);
-        ref->fGenerationID = buffer->readU32();
-        int32_t verbCount = buffer->readS32();
-        int32_t pointCount = buffer->readS32();
-        int32_t conicCount = buffer->readS32();
-        ref->resetToSize(verbCount, pointCount, conicCount);
-
-        SkASSERT(verbCount == ref->countVerbs());
-        SkASSERT(pointCount == ref->countPoints());
-        SkASSERT(conicCount == ref->fConicWeights.count());
-        buffer->read(ref->verbsMemWritable(), verbCount * sizeof(uint8_t));
-        buffer->read(ref->fPoints, pointCount * sizeof(SkPoint));
-        buffer->read(ref->fConicWeights.begin(), conicCount * sizeof(SkScalar));
-        return ref;
-    }
+    static SkPathRef* CreateFromBuffer(SkRBuffer* buffer
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+        , bool newFormat, int32_t oldPacked
+#endif
+        );
 
     /**
      * Rollsback a path ref to zero verbs and points with the assumption that the path ref will be
@@ -182,6 +232,7 @@
     static void Rewind(SkAutoTUnref<SkPathRef>* pathRef) {
         if ((*pathRef)->unique()) {
             (*pathRef)->validate();
+            (*pathRef)->fBoundsIsDirty = true;  // this also invalidates fIsFinite
             (*pathRef)->fVerbCnt = 0;
             (*pathRef)->fPointCnt = 0;
             (*pathRef)->fFreeSpace = (*pathRef)->currSize();
@@ -290,35 +341,26 @@
     /**
      * Writes the path points and verbs to a buffer.
      */
-    void writeToBuffer(SkWBuffer* buffer) {
-        this->validate();
-        SkDEBUGCODE(size_t beforePos = buffer->pos();)
-
-        // TODO: write gen ID here. Problem: We don't know if we're cross process or not from
-        // SkWBuffer. Until this is fixed we write 0.
-        buffer->write32(0);
-        buffer->write32(fVerbCnt);
-        buffer->write32(fPointCnt);
-        buffer->write32(fConicWeights.count());
-        buffer->write(verbsMemBegin(), fVerbCnt * sizeof(uint8_t));
-        buffer->write(fPoints, fPointCnt * sizeof(SkPoint));
-        buffer->write(fConicWeights.begin(), fConicWeights.bytes());
-
-        SkASSERT(buffer->pos() - beforePos == (size_t) this->writeSize());
-    }
+    void writeToBuffer(SkWBuffer* buffer);
 
     /**
      * Gets the number of bytes that would be written in writeBuffer()
      */
     uint32_t writeSize() {
-        return 4 * sizeof(uint32_t) +
+        return 5 * sizeof(uint32_t) +
                fVerbCnt * sizeof(uint8_t) +
                fPointCnt * sizeof(SkPoint) +
-               fConicWeights.bytes();
+               fConicWeights.bytes() +
+               sizeof(SkRect);
     }
 
 private:
+    enum SerializationOffsets {
+        kIsFinite_SerializationShift = 25,  // requires 1 bit
+    };
+
     SkPathRef() {
+        fBoundsIsDirty = true;    // this also invalidates fIsFinite
         fPointCnt = 0;
         fVerbCnt = 0;
         fVerbs = NULL;
@@ -339,9 +381,34 @@
         // We could call genID() here to force a real ID (instead of 0). However, if we're making
         // a copy then presumably we intend to make a modification immediately afterwards.
         fGenerationID = ref.fGenerationID;
+        fBoundsIsDirty = ref.fBoundsIsDirty;
+        if (!fBoundsIsDirty) {
+            fBounds = ref.fBounds;
+            fIsFinite = ref.fIsFinite;
+        }
         this->validate();
     }
 
+    // Return true if the computed bounds are finite.
+    static bool ComputePtBounds(SkRect* bounds, const SkPathRef& ref) {
+        int count = ref.countPoints();
+        if (count <= 1) {  // we ignore just 1 point (moveto)
+            bounds->setEmpty();
+            return count ? ref.points()->isFinite() : true;
+        } else {
+            return bounds->setBoundsCheck(ref.points(), count);
+        }
+    }
+
+    // called, if dirty, by getBounds()
+    void computeBounds() const {
+        SkDEBUGCODE(this->validate();)
+        SkASSERT(fBoundsIsDirty);
+
+        fIsFinite = ComputePtBounds(&fBounds, *this);
+        fBoundsIsDirty = false;
+    }
+
     /** Makes additional room but does not change the counts or change the genID */
     void incReserve(int additionalVerbs, int additionalPoints) {
         this->validate();
@@ -350,11 +417,12 @@
         this->validate();
     }
 
-    /** Resets the path ref with verbCount verbs and pointCount points, all unitialized. Also
+    /** Resets the path ref with verbCount verbs and pointCount points, all uninitialized. Also
      *  allocates space for reserveVerb additional verbs and reservePoints additional points.*/
     void resetToSize(int verbCount, int pointCount, int conicCount,
                      int reserveVerbs = 0, int reservePoints = 0) {
         this->validate();
+        fBoundsIsDirty = true;      // this also invalidates fIsFinite
         fGenerationID = 0;
 
         size_t newSize = sizeof(uint8_t) * verbCount + sizeof(SkPoint) * pointCount;
@@ -394,6 +462,7 @@
         fVerbCnt += newVerbs;
         fPointCnt += newPoints;
         fFreeSpace -= space;
+        fBoundsIsDirty = true;  // this also invalidates fIsFinite
         this->validate();
     }
 
@@ -402,44 +471,7 @@
      * of additional points. A pointer to the first point is returned. Any new points are
      * uninitialized.
      */
-    SkPoint* growForVerb(SkPath::Verb verb) {
-        this->validate();
-        int pCnt;
-        switch (verb) {
-            case SkPath::kMove_Verb:
-                pCnt = 1;
-                break;
-            case SkPath::kLine_Verb:
-                pCnt = 1;
-                break;
-            case SkPath::kQuad_Verb:
-                // fall through
-            case SkPath::kConic_Verb:
-                pCnt = 2;
-                break;
-            case SkPath::kCubic_Verb:
-                pCnt = 3;
-                break;
-            case SkPath::kClose_Verb:
-                pCnt = 0;
-                break;
-            case SkPath::kDone_Verb:
-                SkDEBUGFAIL("growForVerb called for kDone");
-                // fall through
-            default:
-                SkDEBUGFAIL("default is not reached");
-                pCnt = 0;
-        }
-        size_t space = sizeof(uint8_t) + pCnt * sizeof (SkPoint);
-        this->makeSpace(space);
-        this->fVerbs[~fVerbCnt] = verb;
-        SkPoint* ret = fPoints + fPointCnt;
-        fVerbCnt += 1;
-        fPointCnt += pCnt;
-        fFreeSpace -= space;
-        this->validate();
-        return ret;
-    }
+    SkPoint* growForVerb(int /*SkPath::Verb*/ verb);
 
     /**
      * Ensures that the free space available in the path ref is >= size. The verb and point counts
@@ -524,12 +556,30 @@
         SkASSERT(!(NULL == fVerbs && fVerbCnt));
         SkASSERT(this->currSize() ==
                  fFreeSpace + sizeof(SkPoint) * fPointCnt + sizeof(uint8_t) * fVerbCnt);
+
+#ifdef SK_DEBUG
+        if (!fBoundsIsDirty && !fBounds.isEmpty()) {
+            bool isFinite = true;
+            for (int i = 0; i < fPointCnt; ++i) {
+                SkASSERT(fPoints[i].fX >= fBounds.fLeft && fPoints[i].fX <= fBounds.fRight &&
+                         fPoints[i].fY >= fBounds.fTop && fPoints[i].fY <= fBounds.fBottom);
+                if (!fPoints[i].isFinite()) {
+                    isFinite = false;
+                }
+            }
+            SkASSERT(SkToBool(fIsFinite) == isFinite);
+        }
+#endif
     }
 
     enum {
         kMinSize = 256,
     };
 
+    mutable SkRect      fBounds;
+    mutable uint8_t     fBoundsIsDirty;
+    mutable SkBool8     fIsFinite;    // only meaningful if bounds are valid
+
     SkPoint*            fPoints; // points to begining of the allocation
     uint8_t*            fVerbs; // points just past the end of the allocation (verbs grow backwards)
     int                 fVerbCnt;
@@ -546,6 +596,4 @@
     typedef SkRefCnt INHERITED;
 };
 
-SK_DEFINE_INST_COUNT(SkPathRef);
-
 #endif
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 39fd7c0..9866505 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -210,10 +210,14 @@
     // V12: add conics to SkPath, use new SkPathRef flattening
     // V13: add flag to drawBitmapRectToRect
     //      parameterize blurs by sigma rather than radius
+    // V14: Add flags word to PathRef serialization
 #ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V13_AND_ALL_OTHER_INSTANCES_TOO
-    static const uint32_t PRIOR_PICTURE_VERSION = 12;  // TODO: remove when .skps regenerated
+    static const uint32_t PRIOR_PRIOR_PICTURE_VERSION = 12;  // TODO: remove when .skps regenerated
 #endif
-    static const uint32_t PICTURE_VERSION = 13;
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+    static const uint32_t PRIOR_PICTURE_VERSION2 = 13;  // TODO: remove when .skps regenerated
+#endif
+    static const uint32_t PICTURE_VERSION = 14;
 
     // fPlayback, fRecord, fWidth & fHeight are protected to allow derived classes to
     // install their own SkPicturePlayback-derived players,SkPictureRecord-derived
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index ef872dc..f9a17df 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -77,12 +77,12 @@
     (usually a new contour, but not required).
 
     It captures some state about the path up front (i.e. if it already has a
-    cached bounds), and the if it can, it updates the cache bounds explicitly,
+    cached bounds), and then if it can, it updates the cache bounds explicitly,
     avoiding the need to revisit all of the points in getBounds().
 
     It also notes if the path was originally degenerate, and if so, sets
     isConvex to true. Thus it can only be used if the contour being added is
-    convex.
+    convex (which is always true since we only allow the addition of rects).
  */
 class SkAutoPathBoundsUpdate {
 public:
@@ -98,48 +98,33 @@
 
     ~SkAutoPathBoundsUpdate() {
         fPath->setIsConvex(fDegenerate);
-        if (fEmpty) {
-            fPath->fBounds = fRect;
-            fPath->fBoundsIsDirty = false;
-            fPath->fIsFinite = fPath->fBounds.isFinite();
-        } else if (!fDirty) {
-            joinNoEmptyChecks(&fPath->fBounds, fRect);
-            fPath->fBoundsIsDirty = false;
-            fPath->fIsFinite = fPath->fBounds.isFinite();
+        if (fEmpty || fHasValidBounds) {
+            fPath->setBounds(fRect);
         }
     }
 
 private:
     SkPath* fPath;
     SkRect  fRect;
-    bool    fDirty;
+    bool    fHasValidBounds;
     bool    fDegenerate;
     bool    fEmpty;
 
-    // returns true if we should proceed
     void init(SkPath* path) {
+        // Cannot use fRect for our bounds unless we know it is sorted
+        fRect.sort();
         fPath = path;
         // Mark the path's bounds as dirty if (1) they are, or (2) the path
         // is non-finite, and therefore its bounds are not meaningful
-        fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
-        fDegenerate = is_degenerate(*path);
+        fHasValidBounds = path->hasComputedBounds() && path->isFinite();
         fEmpty = path->isEmpty();
-        // Cannot use fRect for our bounds unless we know it is sorted
-        fRect.sort();
+        if (fHasValidBounds && !fEmpty) {
+            joinNoEmptyChecks(&fRect, fPath->getBounds());
+        }
+        fDegenerate = is_degenerate(*path);
     }
 };
 
-// Return true if the computed bounds are finite.
-static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
-    int count = ref.countPoints();
-    if (count <= 1) {  // we ignore just 1 point (moveto)
-        bounds->setEmpty();
-        return count ? ref.points()->isFinite() : true;
-    } else {
-        return bounds->setBoundsCheck(ref.points(), count);
-    }
-}
-
 ////////////////////////////////////////////////////////////////////////////
 
 /*
@@ -173,10 +158,8 @@
     fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
     fFillType = kWinding_FillType;
     fSegmentMask = 0;
-    fBoundsIsDirty = true;
     fConvexity = kUnknown_Convexity;
     fDirection = kUnknown_Direction;
-    fIsFinite = false;
     fIsOval = false;
 #ifdef SK_BUILD_FOR_ANDROID
     GEN_ID_INC;
@@ -216,14 +199,11 @@
 
 void SkPath::copyFields(const SkPath& that) {
     //fPathRef is assumed to have been set by the caller.
-    fBounds          = that.fBounds;
     fLastMoveToIndex = that.fLastMoveToIndex;
     fFillType        = that.fFillType;
     fSegmentMask     = that.fSegmentMask;
-    fBoundsIsDirty   = that.fBoundsIsDirty;
     fConvexity       = that.fConvexity;
     fDirection       = that.fDirection;
-    fIsFinite        = that.fIsFinite;
     fIsOval          = that.fIsOval;
 }
 
@@ -245,14 +225,11 @@
 
     if (this != &that) {
         fPathRef.swap(&that.fPathRef);
-        SkTSwap<SkRect>(fBounds, that.fBounds);
         SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
         SkTSwap<uint8_t>(fFillType, that.fFillType);
         SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
-        SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
         SkTSwap<uint8_t>(fConvexity, that.fConvexity);
         SkTSwap<uint8_t>(fDirection, that.fDirection);
-        SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
         SkTSwap<SkBool8>(fIsOval, that.fIsOval);
 #ifdef SK_BUILD_FOR_ANDROID
         // It doesn't really make sense to swap the generation IDs here, because they might go
@@ -379,11 +356,6 @@
     this->resetFields();
 }
 
-bool SkPath::isEmpty() const {
-    SkDEBUGCODE(this->validate();)
-    return 0 == fPathRef->countVerbs();
-}
-
 bool SkPath::isLine(SkPoint line[2]) const {
     int verbCount = fPathRef->countVerbs();
 
@@ -665,14 +637,6 @@
     }
 }
 
-void SkPath::computeBounds() const {
-    SkDEBUGCODE(this->validate();)
-    SkASSERT(fBoundsIsDirty);
-
-    fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
-    fBoundsIsDirty = false;
-}
-
 void SkPath::setConvexity(Convexity c) {
     if (fConvexity != c) {
         fConvexity = c;
@@ -685,17 +649,11 @@
 
 #define DIRTY_AFTER_EDIT                 \
     do {                                 \
-        fBoundsIsDirty = true;           \
         fConvexity = kUnknown_Convexity; \
         fDirection = kUnknown_Direction; \
         fIsOval = false;                 \
     } while (0)
 
-#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE   \
-    do {                                                    \
-        fBoundsIsDirty = true;                              \
-    } while (0)
-
 void SkPath::incReserve(U16CPU inc) {
     SkDEBUGCODE(this->validate();)
     SkPathRef::Editor(&fPathRef, inc, inc);
@@ -713,7 +671,6 @@
     ed.growForVerb(kMove_Verb)->set(x, y);
 
     GEN_ID_INC;
-    DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
 }
 
 void SkPath::rMoveTo(SkScalar x, SkScalar y) {
@@ -1682,35 +1639,6 @@
         matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
         dst->fDirection = kUnknown_Direction;
     } else {
-        /*
-         *  If we're not in perspective, we can transform all of the points at
-         *  once.
-         *
-         *  Here we also want to optimize bounds, by noting if the bounds are
-         *  already known, and if so, we just transform those as well and mark
-         *  them as "known", rather than force the transformed path to have to
-         *  recompute them.
-         *
-         *  Special gotchas if the path is effectively empty (<= 1 point) or
-         *  if it is non-finite. In those cases bounds need to stay empty,
-         *  regardless of the matrix.
-         */
-        if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
-            dst->fBoundsIsDirty = false;
-            if (fIsFinite) {
-                matrix.mapRect(&dst->fBounds, fBounds);
-                if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
-                    dst->fBounds.setEmpty();
-                }
-            } else {
-                dst->fIsFinite = false;
-                dst->fBounds.setEmpty();
-            }
-        } else {
-            GEN_ID_PTR_INC(dst);
-            dst->fBoundsIsDirty = true;
-        }
-
         SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
 
         if (this != dst) {
@@ -1720,7 +1648,7 @@
         }
 
 #ifdef SK_BUILD_FOR_ANDROID
-        if (!matrix.isIdentity()) {
+        if (!matrix.isIdentity() && !dst->hasComputedBounds()) {
             GEN_ID_PTR_INC(dst);
         }
 #endif
@@ -2089,32 +2017,25 @@
     SkDEBUGCODE(this->validate();)
 
     if (NULL == storage) {
-        const int byteCount = sizeof(int32_t)
-                      + fPathRef->writeSize()
-                      + sizeof(SkRect);
+        const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
         return SkAlign4(byteCount);
     }
 
     SkWBuffer   buffer(storage);
 
-    // Call getBounds() to ensure (as a side-effect) that fBounds
-    // and fIsFinite are computed.
-    const SkRect& bounds = this->getBounds();
-    SkASSERT(!fBoundsIsDirty);
-
-    int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
-                     ((fIsOval & 1) << kIsOval_SerializationShift) |
+    int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
                      (fConvexity << kConvexity_SerializationShift) |
                      (fFillType << kFillType_SerializationShift) |
                      (fSegmentMask << kSegmentMask_SerializationShift) |
-                     (fDirection << kDirection_SerializationShift);
+                     (fDirection << kDirection_SerializationShift)
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+                     | (0x1 << kNewFormat_SerializationShift);
+#endif
 
     buffer.write32(packed);
 
     fPathRef->writeToBuffer(&buffer);
 
-    buffer.write(&bounds, sizeof(bounds));
-
     buffer.padToAlign4();
     return SkToU32(buffer.pos());
 }
@@ -2123,17 +2044,20 @@
     SkRBuffer   buffer(storage);
 
     uint32_t packed = buffer.readS32();
-    fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
     fIsOval = (packed >> kIsOval_SerializationShift) & 1;
     fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
     fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
     fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
     fDirection = (packed >> kDirection_SerializationShift) & 0x3;
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+    bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
+#endif
 
-    fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
-
-    buffer.read(&fBounds, sizeof(fBounds));
-    fBoundsIsDirty = false;
+    fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+        , newFormat, packed)
+#endif
+        );
 
     buffer.skipToAlign4();
 
diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp
new file mode 100644
index 0000000..82d9e1c
--- /dev/null
+++ b/src/core/SkPathRef.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBuffer.h"
+#include "SkPath.h"
+#include "SkPathRef.h"
+
+SK_DEFINE_INST_COUNT(SkPathRef);
+
+SkPoint* SkPathRef::Editor::growForVerb(int /*SkPath::Verb*/ verb) {
+    fPathRef->validate();
+    return fPathRef->growForVerb(verb);
+}
+
+SkPoint* SkPathRef::Editor::growForConic(SkScalar w) {
+    fPathRef->validate();
+    SkPoint* pts = fPathRef->growForVerb(SkPath::kConic_Verb);
+    *fPathRef->fConicWeights.append() = w;
+    return pts;
+}
+
+SkPoint* SkPathRef::growForVerb(int /* SkPath::Verb*/ verb) {
+    this->validate();
+    int pCnt;
+    switch (verb) {
+        case SkPath::kMove_Verb:
+            pCnt = 1;
+            break;
+        case SkPath::kLine_Verb:
+            pCnt = 1;
+            break;
+        case SkPath::kQuad_Verb:
+            // fall through
+        case SkPath::kConic_Verb:
+            pCnt = 2;
+            break;
+        case SkPath::kCubic_Verb:
+            pCnt = 3;
+            break;
+        case SkPath::kClose_Verb:
+            pCnt = 0;
+            break;
+        case SkPath::kDone_Verb:
+            SkDEBUGFAIL("growForVerb called for kDone");
+            // fall through
+        default:
+            SkDEBUGFAIL("default is not reached");
+            pCnt = 0;
+    }
+    size_t space = sizeof(uint8_t) + pCnt * sizeof (SkPoint);
+    this->makeSpace(space);
+    this->fVerbs[~fVerbCnt] = verb;
+    SkPoint* ret = fPoints + fPointCnt;
+    fVerbCnt += 1;
+    fPointCnt += pCnt;
+    fFreeSpace -= space;
+    fBoundsIsDirty = true;  // this also invalidates fIsFinite
+    this->validate();
+    return ret;
+}
+
+SkPathRef* SkPathRef::CreateFromBuffer(SkRBuffer* buffer
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+                                   , bool newFormat, int32_t oldPacked
+#endif
+    ) {
+    SkPathRef* ref = SkNEW(SkPathRef);
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+    if (newFormat) {
+#endif
+        int32_t packed = buffer->readU32();
+
+        ref->fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+    } else {
+        ref->fIsFinite = (oldPacked >> SkPath::kOldIsFinite_SerializationShift) & 1;
+    }
+#endif
+
+    ref->fGenerationID = buffer->readU32();
+    int32_t verbCount = buffer->readS32();
+    int32_t pointCount = buffer->readS32();
+    int32_t conicCount = buffer->readS32();
+    ref->resetToSize(verbCount, pointCount, conicCount);
+
+    SkASSERT(verbCount == ref->countVerbs());
+    SkASSERT(pointCount == ref->countPoints());
+    SkASSERT(conicCount == ref->fConicWeights.count());
+    buffer->read(ref->verbsMemWritable(), verbCount * sizeof(uint8_t));
+    buffer->read(ref->fPoints, pointCount * sizeof(SkPoint));
+    buffer->read(ref->fConicWeights.begin(), conicCount * sizeof(SkScalar));
+    buffer->read(&ref->fBounds, sizeof(SkRect));
+    ref->fBoundsIsDirty = false;
+    return ref;
+}
+
+/**
+ * Writes the path points and verbs to a buffer.
+ */
+void SkPathRef::writeToBuffer(SkWBuffer* buffer) {
+    this->validate();
+    SkDEBUGCODE(size_t beforePos = buffer->pos();)
+
+    // Call getBounds() to ensure (as a side-effect) that fBounds
+    // and fIsFinite are computed.
+    const SkRect& bounds = this->getBounds();
+
+    int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift);
+    buffer->write32(packed);
+
+    // TODO: write gen ID here. Problem: We don't know if we're cross process or not from
+    // SkWBuffer. Until this is fixed we write 0.
+    buffer->write32(0);
+    buffer->write32(fVerbCnt);
+    buffer->write32(fPointCnt);
+    buffer->write32(fConicWeights.count());
+    buffer->write(verbsMemBegin(), fVerbCnt * sizeof(uint8_t));
+    buffer->write(fPoints, fPointCnt * sizeof(SkPoint));
+    buffer->write(fConicWeights.begin(), fConicWeights.bytes());
+    buffer->write(&bounds, sizeof(bounds));
+
+    SkASSERT(buffer->pos() - beforePos == (size_t) this->writeSize());
+}
diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp
index f70f16e..a1aa35e 100644
--- a/src/core/SkPicture.cpp
+++ b/src/core/SkPicture.cpp
@@ -278,7 +278,11 @@
     if (PICTURE_VERSION != info.fVersion
 #ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V13_AND_ALL_OTHER_INSTANCES_TOO
         // V13 is backwards compatible with V12
-        && PRIOR_PICTURE_VERSION != info.fVersion  // TODO: remove when .skps regenerated
+        && PRIOR_PRIOR_PICTURE_VERSION != info.fVersion  // TODO: remove when .skps regenerated
+#endif
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+        // V14 is backwards compatible with V13
+        && PRIOR_PICTURE_VERSION2 != info.fVersion  // TODO: remove when .skps regenerated
 #endif
         ) {
         return false;