Serialize rrect/oval paths as rrects rather than points and verbs.

This is a step towards not trusting deserialized isoval/isrrect for general paths without losing the performance advantages of knowing that a path is a rrect/oval.

Change-Id: I1a8c0608c0f29f4bf7a118dfa1d475e2ab5802ea
Reviewed-on: https://skia-review.googlesource.com/49761
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index 7f52f07..0eb626b 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -1682,20 +1682,25 @@
 
 private:
     enum SerializationOffsets {
-        // 1 free bit at 29
-        kUnused1_SerializationShift = 28,    // 1 free bit
-        kDirection_SerializationShift = 26, // requires 2 bits
+        kType_SerializationShift = 28,       // requires 4 bits
+        kDirection_SerializationShift = 26,  // requires 2 bits
         kIsVolatile_SerializationShift = 25, // requires 1 bit
         // 1 free bit at 24
-        kConvexity_SerializationShift = 16, // requires 8 bits
-        kFillType_SerializationShift = 8,   // requires 8 bits
+        kConvexity_SerializationShift = 16,  // requires 8 bits
+        kFillType_SerializationShift = 8,    // requires 8 bits
         // low-8-bits are version
     };
 
     enum SerializationVersions {
         kPathPrivFirstDirection_Version = 1,
         kPathPrivLastMoveToIndex_Version = 2,
-        kCurrent_Version = 2
+        kPathPrivTypeEnumVersion = 3,
+        kCurrent_Version = 3
+    };
+
+    enum SerializationType {
+        kGeneral = 0,
+        kRRect = 1
     };
 
     sk_sp<SkPathRef>                                   fPathRef;
@@ -1717,6 +1722,9 @@
      */
     void copyFields(const SkPath& that);
 
+    size_t writeToMemoryAsRRect(int32_t packedHeader, void* buffer) const;
+    size_t readFromMemoryAsRRect(const void* buffer) const;
+
     friend class Iter;
     friend class SkPathPriv;
     friend class SkPathStroker;
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
index eb983b6..0a231b6 100644
--- a/include/core/SkRRect.h
+++ b/include/core/SkRRect.h
@@ -13,6 +13,8 @@
 
 class SkPath;
 class SkMatrix;
+class SkRBuffer;
+class SkWBuffer;
 
 // Path forward:
 //   core work
@@ -302,6 +304,7 @@
      *  a multiple of 4. Return kSizeInMemory.
      */
     size_t writeToMemory(void* buffer) const;
+    void writeToBuffer(SkWBuffer*) const;
 
     /**
      * Reads the rrect from the specified buffer
@@ -315,6 +318,7 @@
      *         0 if there was not enough memory available
      */
     size_t readFromMemory(const void* buffer, size_t length);
+    bool readFromBuffer(SkRBuffer*);
 
     /**
      *  Transform by the specified matrix, and put the result in dst.
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 1959221..bcf26c0 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -2040,22 +2040,56 @@
     Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
 */
 
-size_t SkPath::writeToMemory(void* storage) const {
-    SkDEBUGCODE(this->validate();)
-
-    if (nullptr == storage) {
-        const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
-        return SkAlign4(byteCount);
+size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
+    SkRect oval;
+    SkRRect rrect;
+    bool isCCW;
+    unsigned start;
+    if (fPathRef->isOval(&oval, &isCCW, &start)) {
+        rrect.setOval(oval);
+        // Convert to rrect start indices.
+        start *= 2;
+    } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
+        return false;
+    }
+    if (!storage) {
+        // packed header, rrect, start index.
+        return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
     }
 
-    SkWBuffer   buffer(storage);
+    SkWBuffer buffer(storage);
+    // Rewrite header's first direction based on rrect direction.
+    uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
+    packedHeader &= ~(0x3 << kDirection_SerializationShift);
+    packedHeader |= firstDir << kDirection_SerializationShift;
+    packedHeader |= SerializationType::kRRect << kType_SerializationShift;
+    buffer.write32(packedHeader);
+    rrect.writeToBuffer(&buffer);
+    buffer.write32(SkToS32(start));
+    buffer.padToAlign4();
+    return buffer.pos();
+}
+
+size_t SkPath::writeToMemory(void* storage) const {
+    SkDEBUGCODE(this->validate();)
 
     int32_t packed = (fConvexity << kConvexity_SerializationShift) |
                      (fFillType << kFillType_SerializationShift) |
                      (fFirstDirection << kDirection_SerializationShift) |
                      (fIsVolatile << kIsVolatile_SerializationShift) |
                      kCurrent_Version;
+    if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
+        return bytes;
+    }
 
+    SkWBuffer   buffer(storage);
+
+    static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
+    if (nullptr == storage) {
+        // packed header, pathref, start index
+        const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
+        return SkAlign4(byteCount);
+    }
     buffer.write32(packed);
     buffer.write32(fLastMoveToIndex);
 
@@ -2081,13 +2115,51 @@
     }
 
     unsigned version = packed & 0xFF;
+    uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
+    FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
+    if (version >= kPathPrivTypeEnumVersion) {
+        SerializationType type =
+                static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
+        switch (type) {
+            case SerializationType::kRRect: {
+                Direction rrectDir;
+                SkRRect rrect;
+                int32_t start;
+                switch (dir) {
+                    case SkPathPriv::kCW_FirstDirection:
+                        rrectDir = kCW_Direction;
+                        break;
+                    case SkPathPriv::kCCW_FirstDirection:
+                        rrectDir = kCCW_Direction;
+                        break;
+                    default:
+                        return 0;
+                }
+                if (!rrect.readFromBuffer(&buffer)) {
+                    return 0;
+                }
+                if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
+                    return 0;
+                }
+                this->reset();
+                this->addRRect(rrect, rrectDir, SkToUInt(start));
+                this->setFillType(fillType);
+                buffer.skipToAlign4();
+                return buffer.pos();
+            }
+            case SerializationType::kGeneral:
+                // Fall through to general path deserialization
+                break;
+            default:
+                return 0;
+        }
+    }
     if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
         return 0;
     }
 
     fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
-    fFillType = (packed >> kFillType_SerializationShift) & 0x3;
-    uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
+    fFillType = fillType;
     fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
     SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
     if (!pathRef) {
diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp
index f1b85fb..81184b1 100644
--- a/src/core/SkRRect.cpp
+++ b/src/core/SkRRect.cpp
@@ -7,6 +7,7 @@
 
 #include <cmath>
 #include "SkRRect.h"
+#include "SkBuffer.h"
 #include "SkMatrix.h"
 #include "SkScaleToSides.h"
 
@@ -461,6 +462,11 @@
     return kSizeInMemory;
 }
 
+void SkRRect::writeToBuffer(SkWBuffer* buffer) const {
+    // Serialize only the rect and corners, but not the derived type tag.
+    buffer->write(this, kSizeInMemory);
+}
+
 size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
     if (length < kSizeInMemory) {
         return 0;
@@ -478,6 +484,20 @@
     return kSizeInMemory;
 }
 
+bool SkRRect::readFromBuffer(SkRBuffer* buffer) {
+    if (buffer->available() < kSizeInMemory) {
+        return false;
+    }
+    SkRRect readData;
+    buffer->read(&readData, kSizeInMemory);
+    if (!AreRectAndRadiiValid(readData.fRect, readData.fRadii)) {
+        return false;
+    }
+    memcpy(this, &readData, kSizeInMemory);
+    this->computeType();
+    return true;
+}
+
 #include "SkString.h"
 #include "SkStringUtils.h"