Bug 1519739: Attempt to create Direct2D paths on the paint thread. r=rhunt
authorBas Schouten <bschouten@mozilla.com>
Mon, 14 Jan 2019 01:16:17 +0100
changeset 454517 7eac43ea765ebb657f9749a6a8fb2c5c006fae8d
parent 454516 42e89a539b98214d8be40cbc7c7860051599e1b6
child 454518 326b73629e37a82d5a0778fcc232776d40667b66
push id35400
push usercsabou@mozilla.com
push dateSat, 19 Jan 2019 09:59:33 +0000
treeherdermozilla-central@f90bab5af97e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhunt
bugs1519739
milestone66.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 1519739: Attempt to create Direct2D paths on the paint thread. r=rhunt Differential Revision: https://phabricator.services.mozilla.com/D16433
gfx/2d/DrawTargetCapture.cpp
gfx/2d/DrawTargetCapture.h
gfx/2d/DrawTargetD2D1.cpp
gfx/2d/Factory.cpp
gfx/2d/HelpersD2D.h
gfx/2d/PathCapture.cpp
gfx/2d/PathCapture.h
gfx/2d/PathD2D.cpp
gfx/2d/PathHelpers.h
gfx/2d/PathRecording.h
gfx/2d/RecordedEventImpl.h
gfx/2d/ScaledFontDWrite.cpp
gfx/2d/ScaledFontDWrite.h
gfx/2d/Types.h
gfx/2d/moz.build
gfx/thebes/gfxContext.cpp
gfx/thebes/gfxPlatform.h
--- a/gfx/2d/DrawTargetCapture.cpp
+++ b/gfx/2d/DrawTargetCapture.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DrawTargetCapture.h"
 #include "DrawCommand.h"
 #include "DrawCommands.h"
 #include "gfxPlatform.h"
 #include "SourceSurfaceCapture.h"
 #include "FilterNodeCapture.h"
+#include "PathCapture.h"
 
 namespace mozilla {
 namespace gfx {
 
 DrawTargetCaptureImpl::~DrawTargetCaptureImpl() {
   if (mSnapshot && !mSnapshot->hasOneRef()) {
     mSnapshot->DrawTargetWillDestroy();
     mSnapshot = nullptr;
@@ -327,16 +328,25 @@ already_AddRefed<DrawTarget> DrawTargetC
 }
 
 RefPtr<DrawTarget> DrawTargetCaptureImpl::CreateSimilarRasterTarget(
     const IntSize& aSize, SurfaceFormat aFormat) const {
   MOZ_ASSERT(!mRefDT->IsCaptureDT());
   return mRefDT->CreateSimilarDrawTarget(aSize, aFormat);
 }
 
+already_AddRefed<PathBuilder> DrawTargetCaptureImpl::CreatePathBuilder(
+    FillRule aFillRule) const {
+  if (mRefDT->GetBackendType() == BackendType::DIRECT2D1_1) {
+    return MakeRefPtr<PathBuilderCapture>(aFillRule, mRefDT).forget();
+  }
+
+  return mRefDT->CreatePathBuilder(aFillRule);
+}
+
 already_AddRefed<FilterNode> DrawTargetCaptureImpl::CreateFilter(
     FilterType aType) {
   if (mRefDT->GetBackendType() == BackendType::DIRECT2D1_1) {
     return MakeRefPtr<FilterNodeCapture>(aType).forget();
   } else {
     return mRefDT->CreateFilter(aType);
   }
 }
--- a/gfx/2d/DrawTargetCapture.h
+++ b/gfx/2d/DrawTargetCapture.h
@@ -116,19 +116,17 @@ class DrawTargetCaptureImpl : public Dra
   }
 
   virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget(
       const IntSize &aSize, SurfaceFormat aFormat) const override;
   virtual RefPtr<DrawTarget> CreateSimilarRasterTarget(
       const IntSize &aSize, SurfaceFormat aFormat) const override;
 
   virtual already_AddRefed<PathBuilder> CreatePathBuilder(
-      FillRule aFillRule = FillRule::FILL_WINDING) const override {
-    return mRefDT->CreatePathBuilder(aFillRule);
-  }
+      FillRule aFillRule = FillRule::FILL_WINDING) const override;
 
   virtual already_AddRefed<GradientStops> CreateGradientStops(
       GradientStop *aStops, uint32_t aNumStops,
       ExtendMode aExtendMode = ExtendMode::CLAMP) const override {
     return mRefDT->CreateGradientStops(aStops, aNumStops, aExtendMode);
   }
   virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
 
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -7,16 +7,17 @@
 #include <initguid.h>
 #include "DrawTargetD2D1.h"
 #include "FilterNodeSoftware.h"
 #include "GradientStopsD2D.h"
 #include "SourceSurfaceCapture.h"
 #include "SourceSurfaceD2D1.h"
 #include "SourceSurfaceDual.h"
 #include "RadialGradientEffectD2D1.h"
+#include "PathCapture.h"
 
 #include "HelpersD2D.h"
 #include "FilterNodeD2D1.h"
 #include "ExtendInputEffectD2D1.h"
 #include "nsAppRunner.h"
 #include "MainThreadUtils.h"
 
 #include "mozilla/Mutex.h"
@@ -583,21 +584,26 @@ void DrawTargetD2D1::StrokeLine(const Po
                 aStrokeOptions.mLineWidth, strokeStyle);
 
   FinalizeDrawing(aOptions.mCompositionOp, aPattern);
 }
 
 void DrawTargetD2D1::Stroke(const Path *aPath, const Pattern &aPattern,
                             const StrokeOptions &aStrokeOptions,
                             const DrawOptions &aOptions) {
-  if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
+  const Path *path = aPath;
+  if (aPath->GetBackendType() == BackendType::CAPTURE) {
+    path = static_cast<const PathCapture *>(aPath)->GetRealizedPath();
+  }
+
+  if (path->GetBackendType() != BackendType::DIRECT2D1_1) {
     gfxDebug() << *this << ": Ignoring drawing call for incompatible path.";
     return;
   }
-  const PathD2D *d2dPath = static_cast<const PathD2D *>(aPath);
+  const PathD2D *d2dPath = static_cast<const PathD2D *>(path);
 
   PrepareForDrawing(aOptions.mCompositionOp, aPattern);
 
   mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
 
   RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
   RefPtr<ID2D1StrokeStyle> strokeStyle =
       CreateStrokeStyleForOptions(aStrokeOptions);
@@ -605,21 +611,26 @@ void DrawTargetD2D1::Stroke(const Path *
   mDC->DrawGeometry(d2dPath->mGeometry, brush, aStrokeOptions.mLineWidth,
                     strokeStyle);
 
   FinalizeDrawing(aOptions.mCompositionOp, aPattern);
 }
 
 void DrawTargetD2D1::Fill(const Path *aPath, const Pattern &aPattern,
                           const DrawOptions &aOptions) {
-  if (!aPath || aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
+  const Path *path = aPath;
+  if (aPath && aPath->GetBackendType() == BackendType::CAPTURE) {
+    path = static_cast<const PathCapture *>(aPath)->GetRealizedPath();
+  }
+
+  if (!path || path->GetBackendType() != BackendType::DIRECT2D1_1) {
     gfxDebug() << *this << ": Ignoring drawing call for incompatible path.";
     return;
   }
-  const PathD2D *d2dPath = static_cast<const PathD2D *>(aPath);
+  const PathD2D *d2dPath = static_cast<const PathD2D *>(path);
 
   PrepareForDrawing(aOptions.mCompositionOp, aPattern);
 
   mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
 
   RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
 
   mDC->FillGeometry(d2dPath->mGeometry, brush);
@@ -799,25 +810,30 @@ void DrawTargetD2D1::PushClipGeometry(ID
   mTransformDirty = true;
 
   if (CurrentLayer().mClipsArePushed) {
     PushD2DLayer(mDC, clip.mGeometry, clip.mTransform, clip.mIsPixelAligned);
   }
 }
 
 void DrawTargetD2D1::PushClip(const Path *aPath) {
-  if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
+  const Path *path = aPath;
+  if (aPath->GetBackendType() == BackendType::CAPTURE) {
+    path = static_cast<const PathCapture *>(aPath)->GetRealizedPath();
+  }
+
+  if (path->GetBackendType() != BackendType::DIRECT2D1_1) {
     gfxDebug() << *this << ": Ignoring clipping call for incompatible path.";
     return;
   }
   if (!EnsureInitialized()) {
     return;
   }
 
-  RefPtr<PathD2D> pathD2D = static_cast<PathD2D *>(const_cast<Path *>(aPath));
+  RefPtr<PathD2D> pathD2D = static_cast<PathD2D *>(const_cast<Path *>(path));
 
   PushClipGeometry(pathD2D->GetGeometry(), D2DMatrix(mTransform));
 }
 
 void DrawTargetD2D1::PushClipRect(const Rect &aRect) {
   if (!EnsureInitialized()) {
     return;
   }
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -502,16 +502,17 @@ already_AddRefed<DrawTarget> Factory::Cr
   return dt.forget();
 }
 
 bool Factory::DoesBackendSupportDataDrawtarget(BackendType aType) {
   switch (aType) {
     case BackendType::DIRECT2D:
     case BackendType::DIRECT2D1_1:
     case BackendType::RECORDING:
+    case BackendType::CAPTURE:
     case BackendType::NONE:
     case BackendType::BACKEND_LAST:
     case BackendType::WEBRENDER_TEXT:
       return false;
     case BackendType::CAIRO:
     case BackendType::SKIA:
       return true;
   }
--- a/gfx/2d/HelpersD2D.h
+++ b/gfx/2d/HelpersD2D.h
@@ -856,12 +856,99 @@ class DCCommandSink : public ID2D1Comman
   STDMETHODIMP PopLayer() {
     mCtx->PopLayer();
     return S_OK;
   }
 
   ID2D1DeviceContext *mCtx;
 };
 
+class MOZ_STACK_CLASS AutoRestoreFP {
+ public:
+  AutoRestoreFP() {
+    // save the current floating point control word
+    _controlfp_s(&savedFPSetting, 0, 0);
+    UINT unused;
+    // set the floating point control word to its default value
+    _controlfp_s(&unused, _CW_DEFAULT, MCW_PC);
+  }
+  ~AutoRestoreFP() {
+    UINT unused;
+    // restore the saved floating point control word
+    _controlfp_s(&unused, savedFPSetting, MCW_PC);
+  }
+
+ private:
+  UINT savedFPSetting;
+};
+
+// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may
+// get called from D2D with nonstandard floating point settings (see comments in
+// bug 1134549) - use AutoRestoreFP to reset the floating point control word to
+// what we expect
+class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink {
+ public:
+  explicit StreamingGeometrySink(PathSink *aSink) : mSink(aSink) {}
+
+  HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) {
+    if (!aPtr) {
+      return E_POINTER;
+    }
+
+    if (aIID == IID_IUnknown) {
+      *aPtr = static_cast<IUnknown *>(this);
+      return S_OK;
+    } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
+      *aPtr = static_cast<ID2D1SimplifiedGeometrySink *>(this);
+      return S_OK;
+    }
+
+    return E_NOINTERFACE;
+  }
+
+  ULONG STDMETHODCALLTYPE AddRef() { return 1; }
+
+  ULONG STDMETHODCALLTYPE Release() { return 1; }
+
+  // We ignore SetFillMode, this depends on the destination sink.
+  STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) { return; }
+  STDMETHOD_(void, BeginFigure)
+  (D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) {
+    AutoRestoreFP resetFloatingPoint;
+    mSink->MoveTo(ToPoint(aPoint));
+  }
+  STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) {
+    AutoRestoreFP resetFloatingPoint;
+    for (UINT i = 0; i < aCount; i++) {
+      mSink->LineTo(ToPoint(aLines[i]));
+    }
+  }
+  STDMETHOD_(void, AddBeziers)
+  (const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) {
+    AutoRestoreFP resetFloatingPoint;
+    for (UINT i = 0; i < aCount; i++) {
+      mSink->BezierTo(ToPoint(aSegments[i].point1),
+                      ToPoint(aSegments[i].point2),
+                      ToPoint(aSegments[i].point3));
+    }
+  }
+  STDMETHOD(Close)() { /* Should never be called! */
+    return S_OK;
+  }
+  STDMETHOD_(void, SetSegmentFlags)
+  (D2D1_PATH_SEGMENT aFlags) { /* Should never be called! */
+  }
+
+  STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) {
+    AutoRestoreFP resetFloatingPoint;
+    if (aEnd == D2D1_FIGURE_END_CLOSED) {
+      return mSink->Close();
+    }
+  }
+
+ private:
+  PathSink *mSink;
+};
+
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif /* MOZILLA_GFX_HELPERSD2D_H_ */
new file mode 100644
--- /dev/null
+++ b/gfx/2d/PathCapture.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "PathCapture.h"
+
+namespace mozilla {
+namespace gfx {
+
+using namespace std;
+
+void PathBuilderCapture::MoveTo(const Point &aPoint) {
+  PathOp op;
+  op.mType = PathOp::OP_MOVETO;
+  op.mP1 = aPoint;
+  mPathOps.push_back(op);
+  mCurrentPoint = aPoint;
+}
+
+void PathBuilderCapture::LineTo(const Point &aPoint) {
+  PathOp op;
+  op.mType = PathOp::OP_LINETO;
+  op.mP1 = aPoint;
+  mPathOps.push_back(op);
+  mCurrentPoint = aPoint;
+}
+
+void PathBuilderCapture::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);
+  mCurrentPoint = aCP3;
+}
+
+void PathBuilderCapture::QuadraticBezierTo(const Point &aCP1,
+                                           const Point &aCP2) {
+  PathOp op;
+  op.mType = PathOp::OP_QUADRATICBEZIERTO;
+  op.mP1 = aCP1;
+  op.mP2 = aCP2;
+  mPathOps.push_back(op);
+  mCurrentPoint = aCP2;
+}
+
+void PathBuilderCapture::Arc(const Point &aOrigin, float aRadius,
+                             float aStartAngle, float aEndAngle,
+                             bool aAntiClockwise) {
+  PathOp op;
+  op.mType = PathOp::OP_ARC;
+  op.mP1 = aOrigin;
+  op.mRadius = aRadius;
+  op.mStartAngle = aStartAngle;
+  op.mEndAngle = aEndAngle;
+  op.mAntiClockwise = aAntiClockwise;
+  mPathOps.push_back(op);
+}
+
+void PathBuilderCapture::Close() {
+  PathOp op;
+  op.mType = PathOp::OP_CLOSE;
+  mPathOps.push_back(op);
+}
+
+Point PathBuilderCapture::CurrentPoint() const { return mCurrentPoint; }
+
+already_AddRefed<Path> PathBuilderCapture::Finish() {
+  return MakeAndAddRef<PathCapture>(std::move(mPathOps), mFillRule, mDT,
+                                    mCurrentPoint);
+}
+
+already_AddRefed<PathBuilder> PathCapture::CopyToBuilder(
+    FillRule aFillRule) const {
+  RefPtr<PathBuilderCapture> capture = new PathBuilderCapture(aFillRule, mDT);
+  capture->mPathOps = mPathOps;
+  capture->mCurrentPoint = mCurrentPoint;
+  return capture.forget();
+}
+
+already_AddRefed<PathBuilder> PathCapture::TransformedCopyToBuilder(
+    const Matrix &aTransform, FillRule aFillRule) const {
+  RefPtr<PathBuilderCapture> capture = new PathBuilderCapture(aFillRule, mDT);
+  typedef std::vector<PathOp> pathOpVec;
+  for (pathOpVec::const_iterator iter = mPathOps.begin();
+       iter != mPathOps.end(); iter++) {
+    PathOp newPathOp;
+    newPathOp.mType = iter->mType;
+    if (newPathOp.mType == PathOp::OpType::OP_ARC) {
+      struct ArcTransformer {
+        ArcTransformer(pathOpVec &aVector, const Matrix &aTransform)
+            : mVector(&aVector), mTransform(&aTransform) {}
+        void BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) {
+          PathOp newPathOp;
+          newPathOp.mType = PathOp::OP_BEZIERTO;
+          newPathOp.mP1 = mTransform->TransformPoint(aCP1);
+          newPathOp.mP2 = mTransform->TransformPoint(aCP2);
+          newPathOp.mP3 = mTransform->TransformPoint(aCP3);
+          mVector->push_back(newPathOp);
+        }
+        void LineTo(const Point &aPoint) {
+          PathOp newPathOp;
+          newPathOp.mType = PathOp::OP_BEZIERTO;
+          newPathOp.mP1 = mTransform->TransformPoint(aPoint);
+          mVector->push_back(newPathOp);
+        }
+        pathOpVec *mVector;
+        const Matrix *mTransform;
+      };
+
+      ArcTransformer arcTransformer(capture->mPathOps, aTransform);
+      ArcToBezier(&arcTransformer, iter->mP1,
+                  Size(iter->mRadius, iter->mRadius), iter->mStartAngle,
+                  iter->mEndAngle, iter->mAntiClockwise);
+    } else {
+      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);
+      }
+      capture->mPathOps.push_back(newPathOp);
+    }
+  }
+  capture->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint);
+  return capture.forget();
+}
+bool PathCapture::ContainsPoint(const Point &aPoint,
+                                const Matrix &aTransform) const {
+  if (!EnsureRealizedPath()) {
+    return false;
+  }
+  return mRealizedPath->ContainsPoint(aPoint, aTransform);
+}
+
+bool PathCapture::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
+                                      const Point &aPoint,
+                                      const Matrix &aTransform) const {
+  if (!EnsureRealizedPath()) {
+    return false;
+  }
+  return mRealizedPath->StrokeContainsPoint(aStrokeOptions, aPoint, aTransform);
+}
+
+Rect PathCapture::GetBounds(const Matrix &aTransform) const {
+  if (!EnsureRealizedPath()) {
+    return Rect();
+  }
+  return mRealizedPath->GetBounds(aTransform);
+}
+
+Rect PathCapture::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
+                                   const Matrix &aTransform) const {
+  if (!EnsureRealizedPath()) {
+    return Rect();
+  }
+  return mRealizedPath->GetStrokedBounds(aStrokeOptions, aTransform);
+}
+
+void PathCapture::StreamToSink(PathSink *aSink) const {
+  for (const PathOp &op : mPathOps) {
+    switch (op.mType) {
+      case PathOp::OP_MOVETO:
+        aSink->MoveTo(op.mP1);
+        break;
+      case PathOp::OP_LINETO:
+        aSink->LineTo(op.mP1);
+        break;
+      case PathOp::OP_BEZIERTO:
+        aSink->BezierTo(op.mP1, op.mP2, op.mP3);
+        break;
+      case PathOp::OP_QUADRATICBEZIERTO:
+        aSink->QuadraticBezierTo(op.mP1, op.mP2);
+        break;
+      case PathOp::OP_ARC:
+        aSink->Arc(op.mP1, op.mRadius, op.mStartAngle, op.mEndAngle,
+                   op.mAntiClockwise);
+        break;
+      case PathOp::OP_CLOSE:
+        aSink->Close();
+        break;
+    }
+  }
+}
+
+bool PathCapture::EnsureRealizedPath() const {
+  RefPtr<PathBuilder> builder = mDT->CreatePathBuilder(mFillRule);
+  if (!builder) {
+    return false;
+  }
+  StreamToSink(builder);
+  mRealizedPath = builder->Finish();
+  return true;
+}
+
+Path *PathCapture::GetRealizedPath() const {
+  if (!EnsureRealizedPath()) {
+    return nullptr;
+  }
+
+  return mRealizedPath.get();
+}
+
+}  // namespace gfx
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/2d/PathCapture.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_PATHCAPTURE_H_
+#define MOZILLA_GFX_PATHCAPTURE_H_
+
+#include "2D.h"
+#include <vector>
+#include <ostream>
+
+#include "PathHelpers.h"
+
+namespace mozilla {
+namespace gfx {
+
+class PathCapture;
+
+class PathBuilderCapture : public PathBuilder {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCapture, override)
+
+  PathBuilderCapture(FillRule aFillRule, DrawTarget *aDT)
+      : mFillRule(aFillRule), mDT(aDT) {}
+
+  /* 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;
+  /* Add a linesegment to the current figure */
+  virtual void LineTo(const Point &aPoint) override;
+  /* Add a cubic bezier curve to the current figure */
+  virtual void BezierTo(const Point &aCP1, const Point &aCP2,
+                        const Point &aCP3) override;
+  /* Add a quadratic bezier curve to the current figure */
+  virtual void QuadraticBezierTo(const Point &aCP1, const Point &aCP2) override;
+  /* Add an arc to the current figure */
+  virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle,
+                   float aEndAngle, bool aAntiClockwise) override;
+  /* 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;
+
+  /* Point the current subpath is at - or where the next subpath will start
+   * if there is no active subpath.
+   */
+  virtual Point CurrentPoint() const override;
+
+  virtual already_AddRefed<Path> Finish() override;
+
+  virtual BackendType GetBackendType() const override {
+    return BackendType::CAPTURE;
+  }
+
+ private:
+  friend class PathCapture;
+
+  FillRule mFillRule;
+  std::vector<PathOp> mPathOps;
+  Point mCurrentPoint;
+  RefPtr<DrawTarget> mDT;
+};
+
+class PathCapture : public Path {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCapture, override)
+
+  PathCapture(const std::vector<PathOp> aOps, FillRule aFillRule,
+              DrawTarget *aDT, const Point &aCurrentPoint)
+      : mPathOps(aOps),
+        mFillRule(aFillRule),
+        mDT(aDT),
+        mCurrentPoint(aCurrentPoint) {}
+
+  virtual BackendType GetBackendType() const override {
+    return BackendType::CAPTURE;
+  }
+  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;
+  virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
+                                   const Point &aPoint,
+                                   const Matrix &aTransform) const override;
+
+  virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const override;
+
+  virtual Rect GetStrokedBounds(
+      const StrokeOptions &aStrokeOptions,
+      const Matrix &aTransform = Matrix()) const override;
+
+  virtual void StreamToSink(PathSink *aSink) const override;
+
+  virtual FillRule GetFillRule() const override { return mFillRule; }
+
+  Path *GetRealizedPath() const;
+
+ private:
+  bool EnsureRealizedPath() const;
+
+  mutable RefPtr<Path> mRealizedPath;
+  std::vector<PathOp> mPathOps;
+  FillRule mFillRule;
+  RefPtr<DrawTarget> mDT;
+  Point mCurrentPoint;
+};
+
+}  // namespace gfx
+}  // namespace mozilla
+
+#endif /* MOZILLA_GFX_PATHCAPTURE_H_ */
--- a/gfx/2d/PathD2D.cpp
+++ b/gfx/2d/PathD2D.cpp
@@ -86,103 +86,16 @@ class OpeningGeometrySink : public ID2D1
       mNeedsFigureEnded = false;
     }
   }
 
   ID2D1SimplifiedGeometrySink *mSink;
   bool mNeedsFigureEnded;
 };
 
-class MOZ_STACK_CLASS AutoRestoreFP {
- public:
-  AutoRestoreFP() {
-    // save the current floating point control word
-    _controlfp_s(&savedFPSetting, 0, 0);
-    UINT unused;
-    // set the floating point control word to its default value
-    _controlfp_s(&unused, _CW_DEFAULT, MCW_PC);
-  }
-  ~AutoRestoreFP() {
-    UINT unused;
-    // restore the saved floating point control word
-    _controlfp_s(&unused, savedFPSetting, MCW_PC);
-  }
-
- private:
-  UINT savedFPSetting;
-};
-
-// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may
-// get called from D2D with nonstandard floating point settings (see comments in
-// bug 1134549) - use AutoRestoreFP to reset the floating point control word to
-// what we expect
-class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink {
- public:
-  explicit StreamingGeometrySink(PathSink *aSink) : mSink(aSink) {}
-
-  HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) {
-    if (!aPtr) {
-      return E_POINTER;
-    }
-
-    if (aIID == IID_IUnknown) {
-      *aPtr = static_cast<IUnknown *>(this);
-      return S_OK;
-    } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
-      *aPtr = static_cast<ID2D1SimplifiedGeometrySink *>(this);
-      return S_OK;
-    }
-
-    return E_NOINTERFACE;
-  }
-
-  ULONG STDMETHODCALLTYPE AddRef() { return 1; }
-
-  ULONG STDMETHODCALLTYPE Release() { return 1; }
-
-  // We ignore SetFillMode, this depends on the destination sink.
-  STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) { return; }
-  STDMETHOD_(void, BeginFigure)
-  (D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) {
-    AutoRestoreFP resetFloatingPoint;
-    mSink->MoveTo(ToPoint(aPoint));
-  }
-  STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) {
-    AutoRestoreFP resetFloatingPoint;
-    for (UINT i = 0; i < aCount; i++) {
-      mSink->LineTo(ToPoint(aLines[i]));
-    }
-  }
-  STDMETHOD_(void, AddBeziers)
-  (const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) {
-    AutoRestoreFP resetFloatingPoint;
-    for (UINT i = 0; i < aCount; i++) {
-      mSink->BezierTo(ToPoint(aSegments[i].point1),
-                      ToPoint(aSegments[i].point2),
-                      ToPoint(aSegments[i].point3));
-    }
-  }
-  STDMETHOD(Close)() { /* Should never be called! */
-    return S_OK;
-  }
-  STDMETHOD_(void, SetSegmentFlags)
-  (D2D1_PATH_SEGMENT aFlags) { /* Should never be called! */
-  }
-
-  STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) {
-    AutoRestoreFP resetFloatingPoint;
-    if (aEnd == D2D1_FIGURE_END_CLOSED) {
-      return mSink->Close();
-    }
-  }
-
- private:
-  PathSink *mSink;
-};
-
 PathBuilderD2D::~PathBuilderD2D() {}
 
 void PathBuilderD2D::MoveTo(const Point &aPoint) {
   if (mFigureActive) {
     mSink->EndFigure(D2D1_FIGURE_END_OPEN);
     mFigureActive = false;
   }
   EnsureActive(aPoint);
--- a/gfx/2d/PathHelpers.h
+++ b/gfx/2d/PathHelpers.h
@@ -10,16 +10,56 @@
 #include "2D.h"
 #include "UserData.h"
 
 #include <cmath>
 
 namespace mozilla {
 namespace gfx {
 
+struct PathOp {
+  PathOp() {}
+  ~PathOp() {}
+
+  enum OpType {
+    OP_MOVETO = 0,
+    OP_LINETO,
+    OP_BEZIERTO,
+    OP_QUADRATICBEZIERTO,
+    OP_ARC,
+    OP_CLOSE
+  };
+
+  OpType mType;
+  Point mP1;
+#if !defined(__GNUC__) || __GNUC__ >= 7
+  union {
+    struct {
+      Point mP2;
+      Point mP3;
+    };
+    struct {
+      float mRadius;
+      float mStartAngle;
+      float mEndAngle;
+      bool mAntiClockwise;
+    };
+  };
+#else
+  Point mP2;
+  Point mP3;
+  float mRadius;
+  float mStartAngle;
+  float mEndAngle;
+  bool mAntiClockwise;
+#endif
+};
+
+const int32_t sPointCount[] = {1, 1, 3, 2, 0, 0};
+
 // Kappa constant for 90-degree angle
 const Float kKappaFactor = 0.55191497064665766025f;
 
 // Calculate kappa constant for partial curve. The sign of angle in the
 // tangent will actually ensure this is negative for a counter clockwise
 // sweep, so changing signs later isn't needed.
 inline Float ComputeKappaFactor(Float aAngle) {
   return (4.0f / 3.0f) * tanf(aAngle / 4.0f);
--- a/gfx/2d/PathRecording.h
+++ b/gfx/2d/PathRecording.h
@@ -11,33 +11,16 @@
 #include <vector>
 #include <ostream>
 
 #include "PathHelpers.h"
 
 namespace mozilla {
 namespace gfx {
 
-struct PathOp {
-  enum OpType {
-    OP_MOVETO = 0,
-    OP_LINETO,
-    OP_BEZIERTO,
-    OP_QUADRATICBEZIERTO,
-    OP_CLOSE
-  };
-
-  OpType mType;
-  Point mP1;
-  Point mP2;
-  Point mP3;
-};
-
-const int32_t sPointCount[] = {1, 1, 3, 2, 0, 0};
-
 class PathRecording;
 class DrawEventRecorderPrivate;
 
 class PathBuilderRecording : public PathBuilder {
  public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderRecording, override)
 
   PathBuilderRecording(PathBuilder *aBuilder, FillRule aFillRule)
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -2630,16 +2630,19 @@ inline bool RecordedPathCreation::PlayEv
         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;
     }
   }
 
   RefPtr<Path> path = builder->Finish();
   aTranslator->AddPath(mRefPtr, path);
--- a/gfx/2d/ScaledFontDWrite.cpp
+++ b/gfx/2d/ScaledFontDWrite.cpp
@@ -6,16 +6,17 @@
 
 #include "ScaledFontDWrite.h"
 #include "UnscaledFontDWrite.h"
 #include "PathD2D.h"
 #include "gfxFont.h"
 #include "Logging.h"
 #include "mozilla/FontPropertyTypes.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "HelpersD2D.h"
 
 #include "dwrite_3.h"
 
 // Currently, we build with WINVER=0x601 (Win7), which means newer
 // declarations in dwrite_3.h will not be visible. Also, we don't
 // yet have the Fall Creators Update SDK available on build machines,
 // so even with updated WINVER, some of the interfaces we need would
 // not be present.
@@ -186,16 +187,22 @@ SkTypeface* ScaledFontDWrite::CreateSkTy
                                         mForceGDIMode, gamma, contrast);
 }
 #endif
 
 void ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
                                            PathBuilder* aBuilder,
                                            const Matrix* aTransformHint) {
   BackendType backendType = aBuilder->GetBackendType();
+  if (backendType == BackendType::CAPTURE) {
+    StreamingGeometrySink sink(aBuilder);
+    CopyGlyphsToSink(aBuffer, &sink);
+    return;
+  }
+
   if (backendType != BackendType::DIRECT2D &&
       backendType != BackendType::DIRECT2D1_1) {
     ScaledFontBase::CopyGlyphsToBuilder(aBuffer, aBuilder, aTransformHint);
     return;
   }
 
   PathBuilderD2D* pathBuilderD2D = static_cast<PathBuilderD2D*>(aBuilder);
 
@@ -231,17 +238,17 @@ void ScaledFontDWrite::GetGlyphDesignMet
     aGlyphMetrics[i].mHeight =
         (metrics[i].advanceHeight - metrics[i].topSideBearing -
          metrics[i].bottomSideBearing) *
         scaleFactor;
   }
 }
 
 void ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer& aBuffer,
-                                        ID2D1GeometrySink* aSink) {
+                                        ID2D1SimplifiedGeometrySink* aSink) {
   std::vector<UINT16> indices;
   std::vector<FLOAT> advances;
   std::vector<DWRITE_GLYPH_OFFSET> offsets;
   indices.resize(aBuffer.mNumGlyphs);
   advances.resize(aBuffer.mNumGlyphs);
   offsets.resize(aBuffer.mNumGlyphs);
 
   memset(&advances.front(), 0, sizeof(FLOAT) * aBuffer.mNumGlyphs);
--- a/gfx/2d/ScaledFontDWrite.h
+++ b/gfx/2d/ScaledFontDWrite.h
@@ -39,17 +39,18 @@ class ScaledFontDWrite final : public Sc
 
   FontType GetType() const override { return FontType::DWRITE; }
 
   already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer& aBuffer,
                                           const DrawTarget* aTarget) override;
   void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, PathBuilder* aBuilder,
                            const Matrix* aTransformHint) override;
 
-  void CopyGlyphsToSink(const GlyphBuffer& aBuffer, ID2D1GeometrySink* aSink);
+  void CopyGlyphsToSink(const GlyphBuffer& aBuffer,
+                        ID2D1SimplifiedGeometrySink* aSink);
 
   void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices, uint32_t aNumGlyphs,
                              GlyphMetrics* aGlyphMetrics) override;
 
   bool CanSerialize() override { return true; }
 
   bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
 
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -261,16 +261,17 @@ enum class DrawTargetType : int8_t {
 enum class BackendType : int8_t {
   NONE = 0,
   DIRECT2D,  // Used for version independent D2D objects.
   CAIRO,
   SKIA,
   RECORDING,
   DIRECT2D1_1,
   WEBRENDER_TEXT,
+  CAPTURE,  // Used for paths
 
   // Add new entries above this line.
   BACKEND_LAST
 };
 
 enum class FontType : int8_t {
   DWRITE,
   GDI,
--- a/gfx/2d/moz.build
+++ b/gfx/2d/moz.build
@@ -182,16 +182,17 @@ UNIFIED_SOURCES += [
     'FilterNodeSoftware.cpp',
     'FilterProcessing.cpp',
     'FilterProcessingScalar.cpp',
     'ImageScaling.cpp',
     'JobScheduler.cpp',
     'Matrix.cpp',
     'Path.cpp',
     'PathCairo.cpp',
+    'PathCapture.cpp',
     'PathHelpers.cpp',
     'PathRecording.cpp',
     'Quaternion.cpp',
     'RecordedEvent.cpp',
     'Scale.cpp',
     'ScaledFontBase.cpp',
     'SFNTData.cpp',
     'SFNTNameTable.cpp',
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -183,17 +183,18 @@ already_AddRefed<Path> gfxContext::GetPa
   RefPtr<Path> path(mPath);
   return path.forget();
 }
 
 void gfxContext::SetPath(Path* path) {
   MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() ||
              path->GetBackendType() == BackendType::RECORDING ||
              (mDT->GetBackendType() == BackendType::DIRECT2D1_1 &&
-              path->GetBackendType() == BackendType::DIRECT2D));
+              path->GetBackendType() == BackendType::DIRECT2D) ||
+             path->GetBackendType() == BackendType::CAPTURE);
   mPath = path;
   mPathBuilder = nullptr;
   mPathIsRect = false;
   mTransformChanged = false;
 }
 
 void gfxContext::Fill() { Fill(PatternFromState(this)); }
 
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -112,16 +112,18 @@ inline const char* GetBackendName(mozill
     case mozilla::gfx::BackendType::SKIA:
       return "skia";
     case mozilla::gfx::BackendType::RECORDING:
       return "recording";
     case mozilla::gfx::BackendType::DIRECT2D1_1:
       return "direct2d 1.1";
     case mozilla::gfx::BackendType::WEBRENDER_TEXT:
       return "webrender text";
+    case mozilla::gfx::BackendType::CAPTURE:
+      return "capture";
     case mozilla::gfx::BackendType::NONE:
       return "none";
     case mozilla::gfx::BackendType::BACKEND_LAST:
       return "invalid";
   }
   MOZ_CRASH("Incomplete switch");
 }