Bug 1464032 Part 14: Refactor Path recording to record Arc properly. r=jrmuizel
authorBob Owen <bobowencode@gmail.com>
Sun, 02 Dec 2018 14:20:00 +0000
changeset 477765 2fd1ba3d96cd90f32b2858569580cc1c6da976e6
parent 477764 09b2e60abc85df3ae4e8a29a01655383acab14bf
child 477766 2195b79ea888b1e49406406f528870ca9cab1525
push id113373
push userbobowencode@gmail.com
push dateFri, 07 Jun 2019 11:10:59 +0000
treeherdermozilla-inbound@2195b79ea888 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1464032
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1464032 Part 14: Refactor Path recording to record Arc properly. r=jrmuizel This also improves the recording and translation speeds.
gfx/2d/PathHelpers.h
gfx/2d/PathRecording.cpp
gfx/2d/PathRecording.h
gfx/2d/RecordedEvent.h
gfx/2d/RecordedEventImpl.h
gfx/2d/RecordingTypes.h
--- a/gfx/2d/PathHelpers.h
+++ b/gfx/2d/PathHelpers.h
@@ -118,17 +118,17 @@ inline void AcuteArcToBezier(T* aSink, c
                              Float aEndAngle) {
   AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint,
                    ComputeKappaFactor(aEndAngle - aStartAngle));
 }
 
 template <typename T>
 void ArcToBezier(T* aSink, const Point& aOrigin, const Size& aRadius,
                  float aStartAngle, float aEndAngle, bool aAntiClockwise,
-                 float aRotation = 0.0f) {
+                 float aRotation = 0.0f, const Matrix& aTransform = Matrix()) {
   Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f;
 
   // Calculate the total arc we're going to sweep.
   Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection;
 
   // Clockwise we always sweep from the smaller to the larger angle, ccw
   // it's vice versa.
   if (arcSweepLeft < 0) {
@@ -143,16 +143,17 @@ void ArcToBezier(T* aSink, const Point& 
 
   Float currentStartAngle = aStartAngle;
   Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle));
   Matrix transform = Matrix::Scaling(aRadius.width, aRadius.height);
   if (aRotation != 0.0f) {
     transform *= Matrix::Rotation(aRotation);
   }
   transform.PostTranslate(aOrigin);
+  transform *= aTransform;
   aSink->LineTo(transform.TransformPoint(currentStartOffset));
 
   while (arcSweepLeft > 0) {
     Float currentEndAngle =
         currentStartAngle +
         std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection;
     Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle));
 
--- a/gfx/2d/PathRecording.cpp
+++ b/gfx/2d/PathRecording.cpp
@@ -6,107 +6,218 @@
 
 #include "PathRecording.h"
 #include "DrawEventRecorder.h"
 #include "RecordedEventImpl.h"
 
 namespace mozilla {
 namespace gfx {
 
+#define NEXT_PARAMS(_type)                                         \
+  const _type params = *reinterpret_cast<const _type*>(nextByte); \
+  nextByte += sizeof(_type);
+
 using namespace std;
 
+bool PathOps::StreamToSink(PathSink& aPathSink) const {
+  if (mPathData.empty()) {
+    return true;
+  }
+
+  const uint8_t* nextByte = mPathData.data();
+  const uint8_t* end = nextByte + mPathData.size();
+  while (nextByte < end) {
+    const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+    nextByte += sizeof(OpType);
+    switch (opType) {
+      case OpType::OP_MOVETO: {
+        NEXT_PARAMS(Point)
+        aPathSink.MoveTo(params);
+        break;
+      }
+      case OpType::OP_LINETO: {
+        NEXT_PARAMS(Point)
+        aPathSink.LineTo(params);
+        break;
+      }
+      case OpType::OP_BEZIERTO: {
+        NEXT_PARAMS(ThreePoints)
+        aPathSink.BezierTo(params.p1, params.p2, params.p3);
+        break;
+      }
+      case OpType::OP_QUADRATICBEZIERTO: {
+        NEXT_PARAMS(TwoPoints)
+        aPathSink.QuadraticBezierTo(params.p1, params.p2);
+        break;
+      }
+      case OpType::OP_ARC: {
+        NEXT_PARAMS(ArcParams)
+        aPathSink.Arc(params.origin, params.radius, params.startAngle,
+                      params.endAngle, params.antiClockwise);
+        break;
+      }
+      case OpType::OP_CLOSE:
+        aPathSink.Close();
+        break;
+      default:
+        return false;
+    }
+  }
+
+  return true;
+}
+
+PathOps PathOps::TransformedCopy(const Matrix& aTransform) const {
+  PathOps newPathOps;
+  const uint8_t* nextByte = mPathData.data();
+  const uint8_t* end = nextByte + mPathData.size();
+  while (nextByte < end) {
+    const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+    nextByte += sizeof(OpType);
+    switch (opType) {
+      case OpType::OP_MOVETO: {
+        NEXT_PARAMS(Point)
+        newPathOps.MoveTo(aTransform.TransformPoint(params));
+        break;
+      }
+      case OpType::OP_LINETO: {
+        NEXT_PARAMS(Point)
+        newPathOps.LineTo(aTransform.TransformPoint(params));
+        break;
+      }
+      case OpType::OP_BEZIERTO: {
+        NEXT_PARAMS(ThreePoints)
+        newPathOps.BezierTo(aTransform.TransformPoint(params.p1),
+                            aTransform.TransformPoint(params.p2),
+                            aTransform.TransformPoint(params.p3));
+        break;
+      }
+      case OpType::OP_QUADRATICBEZIERTO: {
+        NEXT_PARAMS(TwoPoints)
+        newPathOps.QuadraticBezierTo(aTransform.TransformPoint(params.p1),
+                                     aTransform.TransformPoint(params.p2));
+        break;
+      }
+      case OpType::OP_ARC: {
+        NEXT_PARAMS(ArcParams)
+        ArcToBezier(&newPathOps, params.origin,
+                    gfx::Size(params.radius, params.radius), params.startAngle,
+                    params.endAngle, params.antiClockwise, 0.0f, aTransform);
+        break;
+      }
+      case OpType::OP_CLOSE:
+        newPathOps.Close();
+        break;
+      default:
+        MOZ_CRASH("We control mOpTypes, so this should never happen.");
+    }
+  }
+
+  return newPathOps;
+}
+
+#undef NEXT_PARAMS
+
+size_t PathOps::NumberOfOps() const {
+  size_t size = 0;
+  const uint8_t* nextByte = mPathData.data();
+  const uint8_t* end = nextByte + mPathData.size();
+  while (nextByte < end) {
+    size++;
+    const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+    nextByte += sizeof(OpType);
+    switch (opType) {
+      case OpType::OP_MOVETO:
+        nextByte += sizeof(Point);
+        break;
+      case OpType::OP_LINETO:
+        nextByte += sizeof(Point);
+        break;
+      case OpType::OP_BEZIERTO:
+        nextByte += sizeof(ThreePoints);
+        break;
+      case OpType::OP_QUADRATICBEZIERTO:
+        nextByte += sizeof(TwoPoints);
+        break;
+      case OpType::OP_ARC:
+        nextByte += sizeof(ArcParams);
+        break;
+      case OpType::OP_CLOSE:
+        break;
+      default:
+        MOZ_CRASH("We control mOpTypes, so this should never happen.");
+    }
+  }
+
+  return size;
+}
+
 void PathBuilderRecording::MoveTo(const Point& aPoint) {
-  PathOp op;
-  op.mType = PathOp::OP_MOVETO;
-  op.mP1 = aPoint;
-  mPathOps.push_back(op);
+  mPathOps.MoveTo(aPoint);
   mPathBuilder->MoveTo(aPoint);
 }
 
 void PathBuilderRecording::LineTo(const Point& aPoint) {
-  PathOp op;
-  op.mType = PathOp::OP_LINETO;
-  op.mP1 = aPoint;
-  mPathOps.push_back(op);
+  mPathOps.LineTo(aPoint);
   mPathBuilder->LineTo(aPoint);
 }
 
 void PathBuilderRecording::BezierTo(const Point& aCP1, const Point& aCP2,
                                     const Point& aCP3) {
-  PathOp op;
-  op.mType = PathOp::OP_BEZIERTO;
-  op.mP1 = aCP1;
-  op.mP2 = aCP2;
-  op.mP3 = aCP3;
-  mPathOps.push_back(op);
+  mPathOps.BezierTo(aCP1, aCP2, aCP3);
   mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
 }
 
 void PathBuilderRecording::QuadraticBezierTo(const Point& aCP1,
                                              const Point& aCP2) {
-  PathOp op;
-  op.mType = PathOp::OP_QUADRATICBEZIERTO;
-  op.mP1 = aCP1;
-  op.mP2 = aCP2;
-  mPathOps.push_back(op);
+  mPathOps.QuadraticBezierTo(aCP1, aCP2);
   mPathBuilder->QuadraticBezierTo(aCP1, aCP2);
 }
 
 void PathBuilderRecording::Close() {
-  PathOp op;
-  op.mType = PathOp::OP_CLOSE;
-  mPathOps.push_back(op);
+  mPathOps.Close();
   mPathBuilder->Close();
 }
 
+void PathBuilderRecording::Arc(const Point& aOrigin, float aRadius,
+                               float aStartAngle, float aEndAngle,
+                               bool aAntiClockwise) {
+  mPathOps.Arc(aOrigin, aRadius, aStartAngle, aEndAngle, aAntiClockwise);
+  mPathBuilder->Arc(aOrigin, aRadius, aStartAngle, aEndAngle, aAntiClockwise);
+}
+
 already_AddRefed<Path> PathBuilderRecording::Finish() {
   RefPtr<Path> path = mPathBuilder->Finish();
-  return MakeAndAddRef<PathRecording>(path, mPathOps, mFillRule, mCurrentPoint, mBeginPoint);
+  return MakeAndAddRef<PathRecording>(path, std::move(mPathOps), mFillRule,
+                                      mCurrentPoint, mBeginPoint);
 }
 
 PathRecording::~PathRecording() {
   for (size_t i = 0; i < mStoredRecorders.size(); i++) {
     mStoredRecorders[i]->RemoveStoredObject(this);
     mStoredRecorders[i]->RecordEvent(RecordedPathDestruction(this));
   }
 }
 
 already_AddRefed<PathBuilder> PathRecording::CopyToBuilder(
     FillRule aFillRule) const {
   RefPtr<PathBuilder> pathBuilder = mPath->CopyToBuilder(aFillRule);
   RefPtr<PathBuilderRecording> recording =
-      new PathBuilderRecording(pathBuilder, aFillRule);
-  recording->mPathOps = mPathOps;
+      new PathBuilderRecording(pathBuilder, mPathOps, aFillRule);
   recording->SetCurrentPoint(mCurrentPoint);
   recording->SetBeginPoint(mBeginPoint);
   return recording.forget();
 }
 
 already_AddRefed<PathBuilder> PathRecording::TransformedCopyToBuilder(
     const Matrix& aTransform, FillRule aFillRule) const {
   RefPtr<PathBuilder> pathBuilder =
       mPath->TransformedCopyToBuilder(aTransform, aFillRule);
-  RefPtr<PathBuilderRecording> recording =
-      new PathBuilderRecording(pathBuilder, aFillRule);
-  typedef std::vector<PathOp> pathOpVec;
-  for (pathOpVec::const_iterator iter = mPathOps.begin();
-       iter != mPathOps.end(); iter++) {
-    PathOp newPathOp;
-    newPathOp.mType = iter->mType;
-    if (sPointCount[newPathOp.mType] >= 1) {
-      newPathOp.mP1 = aTransform.TransformPoint(iter->mP1);
-    }
-    if (sPointCount[newPathOp.mType] >= 2) {
-      newPathOp.mP2 = aTransform.TransformPoint(iter->mP2);
-    }
-    if (sPointCount[newPathOp.mType] >= 3) {
-      newPathOp.mP3 = aTransform.TransformPoint(iter->mP3);
-    }
-    recording->mPathOps.push_back(newPathOp);
-  }
+  RefPtr<PathBuilderRecording> recording = new PathBuilderRecording(
+      pathBuilder, mPathOps.TransformedCopy(aTransform), aFillRule);
 
   recording->SetCurrentPoint(aTransform.TransformPoint(mCurrentPoint));
   recording->SetBeginPoint(aTransform.TransformPoint(mBeginPoint));
 
   return recording.forget();
 }
 
 }  // namespace gfx
--- a/gfx/2d/PathRecording.h
+++ b/gfx/2d/PathRecording.h
@@ -7,141 +7,236 @@
 #ifndef MOZILLA_GFX_PATHRECORDING_H_
 #define MOZILLA_GFX_PATHRECORDING_H_
 
 #include "2D.h"
 #include <vector>
 #include <ostream>
 
 #include "PathHelpers.h"
+#include "RecordingTypes.h"
 
 namespace mozilla {
 namespace gfx {
 
+class PathOps {
+ public:
+  PathOps() {}
+
+  template <class S>
+  explicit PathOps(S& aStream);
+
+  PathOps(PathOps&& aOther) : mPathData(std::move(aOther.mPathData)) {}
+
+  PathOps(const PathOps& aOther) : mPathData(aOther.mPathData) {}
+
+  PathOps& operator=(PathOps&& aOther) {
+    mPathData = std::move(aOther.mPathData);
+    return *this;
+  }
+
+  template <class S>
+  void Record(S& aStream) const;
+
+  bool StreamToSink(PathSink& aPathSink) const;
+
+  PathOps TransformedCopy(const Matrix& aTransform) const;
+
+  size_t NumberOfOps() const;
+
+  void MoveTo(const Point& aPoint) { AppendPathOp(OpType::OP_MOVETO, aPoint); }
+
+  void LineTo(const Point& aPoint) { AppendPathOp(OpType::OP_LINETO, aPoint); }
+
+  void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) {
+    AppendPathOp(OpType::OP_BEZIERTO, ThreePoints{aCP1, aCP2, aCP3});
+  }
+
+  void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
+    AppendPathOp(OpType::OP_QUADRATICBEZIERTO, TwoPoints{aCP1, aCP2});
+  }
+
+  void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+           float aEndAngle, bool aAntiClockwise) {
+    AppendPathOp(OpType::OP_ARC, ArcParams{aOrigin, aRadius, aStartAngle,
+                                           aEndAngle, aAntiClockwise});
+  }
+
+  void Close() {
+    size_t oldSize = mPathData.size();
+    mPathData.resize(oldSize + sizeof(OpType));
+    *reinterpret_cast<OpType*>(mPathData.data() + oldSize) = OpType::OP_CLOSE;
+  }
+
+ private:
+  void operator=(const PathOps&) = delete;  // assign using std::move()!
+
+  enum class OpType : uint32_t {
+    OP_MOVETO = 0,
+    OP_LINETO,
+    OP_BEZIERTO,
+    OP_QUADRATICBEZIERTO,
+    OP_ARC,
+    OP_CLOSE,
+    OP_INVALID
+  };
+
+  template <typename T>
+  void AppendPathOp(const OpType& aOpType, const T& aOpParams) {
+    size_t oldSize = mPathData.size();
+    mPathData.resize(oldSize + sizeof(OpType) + sizeof(T));
+    memcpy(mPathData.data() + oldSize, &aOpType, sizeof(OpType));
+    oldSize += sizeof(OpType);
+    memcpy(mPathData.data() + oldSize, &aOpParams, sizeof(T));
+  }
+
+  struct TwoPoints {
+    Point p1;
+    Point p2;
+  };
+
+  struct ThreePoints {
+    Point p1;
+    Point p2;
+    Point p3;
+  };
+
+  struct ArcParams {
+    Point origin;
+    float radius;
+    float startAngle;
+    float endAngle;
+    bool antiClockwise;
+  };
+
+  std::vector<uint8_t> mPathData;
+};
+
+template <class S>
+PathOps::PathOps(S& aStream) {
+  ReadVector(aStream, mPathData);
+}
+
+template <class S>
+inline void PathOps::Record(S& aStream) const {
+  WriteVector(aStream, mPathData);
+}
+
 class PathRecording;
 class DrawEventRecorderPrivate;
 
-class PathBuilderRecording : public PathBuilder {
+class PathBuilderRecording final : public PathBuilder {
  public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderRecording, override)
 
   PathBuilderRecording(PathBuilder* aBuilder, FillRule aFillRule)
       : mPathBuilder(aBuilder), mFillRule(aFillRule) {}
 
+  PathBuilderRecording(PathBuilder* aBuilder, const PathOps& aPathOps,
+                       FillRule aFillRule)
+      : mPathBuilder(aBuilder), mFillRule(aFillRule), mPathOps(aPathOps) {}
+
   /* Move the current point in the path, any figure currently being drawn will
    * be considered closed during fill operations, however when stroking the
    * closing line segment will not be drawn.
    */
-  virtual void MoveTo(const Point& aPoint) override;
+  void MoveTo(const Point& aPoint) final;
+
   /* Add a linesegment to the current figure */
-  virtual void LineTo(const Point& aPoint) override;
+  void LineTo(const Point& aPoint) final;
+
   /* Add a cubic bezier curve to the current figure */
-  virtual void BezierTo(const Point& aCP1, const Point& aCP2,
-                        const Point& aCP3) override;
+  void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) final;
+
   /* Add a quadratic bezier curve to the current figure */
-  virtual void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override;
+  void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) final;
+
   /* Close the current figure, this will essentially generate a line segment
    * from the current point to the starting point for the current figure
    */
-  virtual void Close() override;
+  void Close() final;
 
   /* Add an arc to the current figure */
-  virtual void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
-                   float aEndAngle, bool aAntiClockwise) override {
-    ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
-                aAntiClockwise);
-  }
+  void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+           float aEndAngle, bool aAntiClockwise) final;
 
   /* Point the current subpath is at - or where the next subpath will start
    * if there is no active subpath.
    */
-  virtual Point CurrentPoint() const override {
+  Point CurrentPoint() const final {
     return mPathBuilder->CurrentPoint();
   }
 
-  virtual Point BeginPoint() const override {
+  Point BeginPoint() const final {
     return mPathBuilder->BeginPoint();
   }
 
-  virtual void SetCurrentPoint(const Point& aPoint) override {
+  void SetCurrentPoint(const Point& aPoint) final {
     mPathBuilder->SetCurrentPoint(aPoint);
   }
 
-  virtual void SetBeginPoint(const Point& aPoint) override {
+  void SetBeginPoint(const Point& aPoint) final {
     mPathBuilder->SetBeginPoint(aPoint);
   }
 
-  virtual already_AddRefed<Path> Finish() override;
+  already_AddRefed<Path> Finish() final;
 
-  virtual BackendType GetBackendType() const override {
-    return BackendType::RECORDING;
-  }
+  BackendType GetBackendType() const final { return BackendType::RECORDING; }
 
  private:
-  friend class PathRecording;
-
   RefPtr<PathBuilder> mPathBuilder;
   FillRule mFillRule;
-  std::vector<PathOp> mPathOps;
+  PathOps mPathOps;
 };
 
-class PathRecording : public Path {
+class PathRecording final : public Path {
  public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathRecording, override)
 
-  PathRecording(Path* aPath, const std::vector<PathOp> aOps, FillRule aFillRule,
+  PathRecording(Path* aPath, PathOps&& aOps, FillRule aFillRule,
                 const Point& aCurrentPoint, const Point& aBeginPoint)
-      : mPath(aPath), mPathOps(aOps), mFillRule(aFillRule),
+      : mPath(aPath), mPathOps(std::move(aOps)), mFillRule(aFillRule),
         mCurrentPoint(aCurrentPoint), mBeginPoint(aBeginPoint) {}
 
   ~PathRecording();
 
-  virtual BackendType GetBackendType() const override {
-    return BackendType::RECORDING;
-  }
-  virtual already_AddRefed<PathBuilder> CopyToBuilder(
-      FillRule aFillRule) const override;
-  virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(
-      const Matrix& aTransform, FillRule aFillRule) const override;
-  virtual bool ContainsPoint(const Point& aPoint,
-                             const Matrix& aTransform) const override {
+  BackendType GetBackendType() const final { return BackendType::RECORDING; }
+  already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const final;
+  already_AddRefed<PathBuilder> TransformedCopyToBuilder(
+      const Matrix& aTransform, FillRule aFillRule) const final;
+  bool ContainsPoint(const Point& aPoint,
+                     const Matrix& aTransform) const final {
     return mPath->ContainsPoint(aPoint, aTransform);
   }
-  virtual bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
-                                   const Point& aPoint,
-                                   const Matrix& aTransform) const override {
+  bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
+                           const Point& aPoint,
+                           const Matrix& aTransform) const final {
     return mPath->StrokeContainsPoint(aStrokeOptions, aPoint, aTransform);
   }
 
-  virtual Rect GetBounds(const Matrix& aTransform = Matrix()) const override {
+  Rect GetBounds(const Matrix& aTransform = Matrix()) const final {
     return mPath->GetBounds(aTransform);
   }
 
-  virtual Rect GetStrokedBounds(
-      const StrokeOptions& aStrokeOptions,
-      const Matrix& aTransform = Matrix()) const override {
+  Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions,
+                        const Matrix& aTransform = Matrix()) const final {
     return mPath->GetStrokedBounds(aStrokeOptions, aTransform);
   }
 
-  virtual void StreamToSink(PathSink* aSink) const override {
-    mPath->StreamToSink(aSink);
-  }
+  void StreamToSink(PathSink* aSink) const final { mPath->StreamToSink(aSink); }
 
-  virtual FillRule GetFillRule() const override { return mFillRule; }
-
-  void StorePath(std::ostream& aStream) const;
-  static void ReadPathToBuilder(std::istream& aStream, PathBuilder* aBuilder);
+  FillRule GetFillRule() const final { return mFillRule; }
 
  private:
   friend class DrawTargetWrapAndRecord;
   friend class DrawTargetRecording;
   friend class RecordedPathCreation;
 
   RefPtr<Path> mPath;
-  std::vector<PathOp> mPathOps;
+  PathOps mPathOps;
   FillRule mFillRule;
   Point mCurrentPoint;
   Point mBeginPoint;
 
   // Event recorders that have this path in their event stream.
   std::vector<RefPtr<DrawEventRecorderPrivate>> mStoredRecorders;
 };
 
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -13,19 +13,16 @@
 #include <cstring>
 #include <vector>
 
 #include "RecordingTypes.h"
 
 namespace mozilla {
 namespace gfx {
 
-struct PathOp;
-class PathRecording;
-
 const uint32_t kMagicInt = 0xc001feed;
 
 // A change in major revision means a change in event binary format, causing
 // loss of backwards compatibility. Old streams will not work in a player
 // using a newer major revision. And new streams will not work in a player
 // using an older major revision.
 const uint16_t kMajorRevision = 10;
 // A change in minor revision means additions of new events. New streams will
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -804,32 +804,32 @@ class RecordedDrawFilter : public Record
   Rect mSourceRect;
   Point mDestPoint;
   DrawOptions mOptions;
 };
 
 class RecordedPathCreation : public RecordedEventDerived<RecordedPathCreation> {
  public:
   MOZ_IMPLICIT RecordedPathCreation(PathRecording* aPath);
-  ~RecordedPathCreation();
 
   bool PlayEvent(Translator* aTranslator) const override;
 
   template <class S>
   void Record(S& aStream) const;
   void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
 
   std::string GetName() const override { return "Path Creation"; }
 
  private:
   friend class RecordedEvent;
 
   ReferencePtr mRefPtr;
   FillRule mFillRule;
-  std::vector<PathOp> mPathOps;
+  RefPtr<PathRecording> mPath;
+  UniquePtr<PathOps> mPathOps;
 
   template <class S>
   MOZ_IMPLICIT RecordedPathCreation(S& aStream);
 };
 
 class RecordedPathDestruction
     : public RecordedEventDerived<RecordedPathDestruction> {
  public:
@@ -2663,104 +2663,51 @@ inline void RecordedDrawSurfaceWithShado
                 << ") Color: (" << mColor.r << ", " << mColor.g << ", "
                 << mColor.b << ", " << mColor.a << ")";
 }
 
 inline RecordedPathCreation::RecordedPathCreation(PathRecording* aPath)
     : RecordedEventDerived(PATHCREATION),
       mRefPtr(aPath),
       mFillRule(aPath->mFillRule),
-      mPathOps(aPath->mPathOps) {}
-
-inline RecordedPathCreation::~RecordedPathCreation() {}
+      mPath(aPath) {}
 
 inline bool RecordedPathCreation::PlayEvent(Translator* aTranslator) const {
   RefPtr<PathBuilder> builder =
       aTranslator->GetReferenceDrawTarget()->CreatePathBuilder(mFillRule);
-
-  for (size_t i = 0; i < mPathOps.size(); i++) {
-    const PathOp& op = mPathOps[i];
-    switch (op.mType) {
-      case PathOp::OP_MOVETO:
-        builder->MoveTo(op.mP1);
-        break;
-      case PathOp::OP_LINETO:
-        builder->LineTo(op.mP1);
-        break;
-      case PathOp::OP_BEZIERTO:
-        builder->BezierTo(op.mP1, op.mP2, op.mP3);
-        break;
-      case PathOp::OP_QUADRATICBEZIERTO:
-        builder->QuadraticBezierTo(op.mP1, op.mP2);
-        break;
-      case PathOp::OP_ARC:
-        MOZ_ASSERT_UNREACHABLE("Recordings should not contain arc operations");
-        break;
-      case PathOp::OP_CLOSE:
-        builder->Close();
-        break;
-    }
+  if (!mPathOps->StreamToSink(*builder)) {
+    return false;
   }
 
   RefPtr<Path> path = builder->Finish();
   aTranslator->AddPath(mRefPtr, path);
   return true;
 }
 
 template <class S>
 void RecordedPathCreation::Record(S& aStream) const {
   WriteElement(aStream, mRefPtr);
-  WriteElement(aStream, uint64_t(mPathOps.size()));
   WriteElement(aStream, mFillRule);
-  typedef std::vector<PathOp> pathOpVec;
-  for (pathOpVec::const_iterator iter = mPathOps.begin();
-       iter != mPathOps.end(); iter++) {
-    WriteElement(aStream, iter->mType);
-    if (sPointCount[iter->mType] >= 1) {
-      WriteElement(aStream, iter->mP1);
-    }
-    if (sPointCount[iter->mType] >= 2) {
-      WriteElement(aStream, iter->mP2);
-    }
-    if (sPointCount[iter->mType] >= 3) {
-      WriteElement(aStream, iter->mP3);
-    }
-  }
+  mPath->mPathOps.Record(aStream);
 }
 
 template <class S>
 RecordedPathCreation::RecordedPathCreation(S& aStream)
     : RecordedEventDerived(PATHCREATION) {
-  uint64_t size;
-
   ReadElement(aStream, mRefPtr);
-  ReadElement(aStream, size);
   ReadElement(aStream, mFillRule);
-
-  for (uint64_t i = 0; i < size; i++) {
-    PathOp newPathOp;
-    ReadElement(aStream, newPathOp.mType);
-    if (sPointCount[newPathOp.mType] >= 1) {
-      ReadElement(aStream, newPathOp.mP1);
-    }
-    if (sPointCount[newPathOp.mType] >= 2) {
-      ReadElement(aStream, newPathOp.mP2);
-    }
-    if (sPointCount[newPathOp.mType] >= 3) {
-      ReadElement(aStream, newPathOp.mP3);
-    }
-
-    mPathOps.push_back(newPathOp);
-  }
+  mPathOps = MakeUnique<PathOps>(aStream);
 }
 
 inline void RecordedPathCreation::OutputSimpleEventInfo(
     std::stringstream& aStringStream) const {
-  aStringStream << "[" << mRefPtr
-                << "] Path created (OpCount: " << mPathOps.size() << ")";
+  size_t numberOfOps =
+      mPath ? mPath->mPathOps.NumberOfOps() : mPathOps->NumberOfOps();
+  aStringStream << "[" << mRefPtr << "] Path created (OpCount: " << numberOfOps
+                << ")";
 }
 inline bool RecordedPathDestruction::PlayEvent(Translator* aTranslator) const {
   aTranslator->RemovePath(mRefPtr);
   return true;
 }
 
 template <class S>
 void RecordedPathDestruction::Record(S& aStream) const {
--- a/gfx/2d/RecordingTypes.h
+++ b/gfx/2d/RecordingTypes.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_RECORDINGTYPES_H_
 #define MOZILLA_GFX_RECORDINGTYPES_H_
 
 #include <ostream>
+#include <vector>
 
 namespace mozilla {
 namespace gfx {
 
 template <class S, class T>
 struct ElementStreamFormat {
   static void Write(S& aStream, const T& aElement) {
     aStream.write(reinterpret_cast<const char*>(&aElement), sizeof(T));
@@ -22,16 +23,36 @@ struct ElementStreamFormat {
   }
 };
 
 template <class S, class T>
 void WriteElement(S& aStream, const T& aElement) {
   ElementStreamFormat<S, T>::Write(aStream, aElement);
 }
 template <class S, class T>
+void WriteVector(S& aStream, const std::vector<T>& aVector) {
+  size_t size = aVector.size();
+  WriteElement(aStream, size);
+  if (size) {
+    aStream.write(reinterpret_cast<const char*>(aVector.data()),
+                  sizeof(T) * size);
+  }
+}
+template <class S, class T>
 void ReadElement(S& aStream, T& aElement) {
   ElementStreamFormat<S, T>::Read(aStream, aElement);
 }
+template <class S, class T>
+void ReadVector(S& aStream, std::vector<T>& aVector) {
+  size_t size;
+  ReadElement(aStream, size);
+  if (size) {
+    aVector.resize(size);
+    aStream.read(reinterpret_cast<char*>(aVector.data()), sizeof(T) * size);
+  } else {
+    aVector.clear();
+  }
+}
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif /* MOZILLA_GFX_RECORDINGTYPES_H_ */