Add initial CurveMeasure code
authorhstern <hstern@google.com>
Mon, 8 Aug 2016 19:28:13 +0000 (12:28 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 8 Aug 2016 19:28:13 +0000 (12:28 -0700)
- This code is entirely private and is not being used by anything.

- In a future CL we will write a class that uses CurveMeasure to compute dash points. In order to determine whether CurveMeasure or PathMeasure should be faster, we need the dash info (the sum of the on/off intervals and how many there are)

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2187083002

Review-Url: https://codereview.chromium.org/2187083002

bench/MeasureBench.cpp [new file with mode: 0644]
gyp/utils.gypi
src/utils/SkCurveMeasure.cpp [new file with mode: 0644]
src/utils/SkCurveMeasure.h [new file with mode: 0644]

diff --git a/bench/MeasureBench.cpp b/bench/MeasureBench.cpp
new file mode 100644 (file)
index 0000000..d76bd35
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// for std::max
+#include <algorithm>
+
+#include "Benchmark.h"
+#include "SkCurveMeasure.h"
+#include "SkPath.h"
+#include "SkPathMeasure.h"
+#include "SkString.h"
+
+#define NORMALIZE_LOOPS
+
+class MeasureBench : public Benchmark {
+   protected:
+    SkString fName;
+
+    SkPath fPath;
+
+    bool fUsePathMeasure;
+    float fSize;
+    size_t fPieces;
+
+    SkPoint fPts[3];
+
+   public:
+    MeasureBench(bool usePathMeasure, float size, size_t pieces)
+        : fUsePathMeasure(usePathMeasure),
+          fSize(size),
+          fPieces(pieces) {
+        fName.printf("measure_%s_%.1f_" SK_SIZE_T_SPECIFIER,
+                     fUsePathMeasure ? "pathMeasure" : "curveMeasure", fSize,
+                     fPieces);
+
+        auto p1 = SkPoint::Make(0, 0);
+        auto p2 = SkPoint::Make(30*fSize, 0);
+        auto p3 = SkPoint::Make(15*fSize, 15*fSize);
+
+        fPts[0] = p1;
+        fPts[1] = p2;
+        fPts[2] = p3;
+
+        this->setPath();
+    }
+
+   protected:
+    const char* onGetName() override { return fName.c_str(); }
+
+    void setPath() {
+        fPath.moveTo(fPts[0]);
+        fPath.quadTo(fPts[1], fPts[2]);
+    }
+
+    int numLoops() {
+#ifdef NORMALIZE_LOOPS
+        // arbitrary heuristic
+        return std::max(2, 10000 / ((int)fSize*(int)fPieces));
+#else
+        return 1000;
+#endif // NORMALIZE_LOOPS
+    }
+
+    //// measurement code
+
+    void do_pathMeasure(SkCanvas* canvas) {
+        SkPathMeasure meas(fPath, false);
+
+        SkScalar totalLength = meas.getLength();
+        SkScalar pieceLength = totalLength / fPieces;
+
+        SkPoint point;
+        for (size_t i = 0; i <= fPieces; i++) {
+            if (meas.getPosTan(i * pieceLength, &point, nullptr)) {
+            };
+        }
+    }
+
+    void do_curveMeasure(SkCanvas* canvas) {
+        SkCurveMeasure meas(fPts, kQuad_SegType);
+
+        SkScalar totalLength = meas.getLength();
+        SkScalar pieceLength = totalLength / fPieces;
+
+        SkPoint point;
+        for (size_t i = 0; i <= fPieces; i++) {
+            meas.getPosTan(i*pieceLength, &point, nullptr);
+        }
+    }
+
+    void onDraw(int loops, SkCanvas* canvas) override {
+        int inner_loops = numLoops();
+        for (int i = 0; i < loops; i++) {
+            for (int j = 0; j < inner_loops; j++) {
+                if (fUsePathMeasure) {
+                    do_pathMeasure(canvas);
+                }
+                else {
+                    do_curveMeasure(canvas);
+                }
+            }
+        }
+    }
+
+   private:
+    typedef Benchmark INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_BENCH(return new MeasureBench(true, 1, 2);)
+DEF_BENCH(return new MeasureBench(true, 2, 2);)
+DEF_BENCH(return new MeasureBench(true, 10, 2);)
+DEF_BENCH(return new MeasureBench(true, 100, 2);)
+DEF_BENCH(return new MeasureBench(true, 1000, 2);)
+
+DEF_BENCH(return new MeasureBench(true, 1, 1);)
+DEF_BENCH(return new MeasureBench(true, 1, 2);)
+DEF_BENCH(return new MeasureBench(true, 1, 3);)
+DEF_BENCH(return new MeasureBench(true, 1, 4);)
+DEF_BENCH(return new MeasureBench(true, 1, 5);)
+DEF_BENCH(return new MeasureBench(true, 2, 1);)
+DEF_BENCH(return new MeasureBench(true, 2, 2);)
+DEF_BENCH(return new MeasureBench(true, 2, 3);)
+DEF_BENCH(return new MeasureBench(true, 2, 4);)
+DEF_BENCH(return new MeasureBench(true, 2, 5);)
+DEF_BENCH(return new MeasureBench(true, 10, 10);)
+DEF_BENCH(return new MeasureBench(true, 10, 20);)
+DEF_BENCH(return new MeasureBench(true, 10, 30);)
+DEF_BENCH(return new MeasureBench(true, 10, 40);)
+DEF_BENCH(return new MeasureBench(true, 10, 50);)
+DEF_BENCH(return new MeasureBench(true, 100, 100);)
+DEF_BENCH(return new MeasureBench(true, 100, 200);)
+DEF_BENCH(return new MeasureBench(true, 100, 300);)
+DEF_BENCH(return new MeasureBench(true, 100, 400);)
+DEF_BENCH(return new MeasureBench(true, 100, 500);)
+DEF_BENCH(return new MeasureBench(true, 1000, 1000);)
+DEF_BENCH(return new MeasureBench(true, 1000, 2000);)
+DEF_BENCH(return new MeasureBench(true, 1000, 3000);)
+DEF_BENCH(return new MeasureBench(true, 1000, 4000);)
+DEF_BENCH(return new MeasureBench(true, 1000, 5000);)
+
+DEF_BENCH(return new MeasureBench(false, 1, 2);)
+DEF_BENCH(return new MeasureBench(false, 2, 2);)
+DEF_BENCH(return new MeasureBench(false, 10, 2);)
+DEF_BENCH(return new MeasureBench(false, 100, 2);)
+DEF_BENCH(return new MeasureBench(false, 1000, 2);)
+
+DEF_BENCH(return new MeasureBench(false, 1, 1);)
+DEF_BENCH(return new MeasureBench(false, 1, 2);)
+DEF_BENCH(return new MeasureBench(false, 1, 3);)
+DEF_BENCH(return new MeasureBench(false, 1, 4);)
+DEF_BENCH(return new MeasureBench(false, 1, 5);)
+DEF_BENCH(return new MeasureBench(false, 2, 1);)
+DEF_BENCH(return new MeasureBench(false, 2, 2);)
+DEF_BENCH(return new MeasureBench(false, 2, 3);)
+DEF_BENCH(return new MeasureBench(false, 2, 4);)
+DEF_BENCH(return new MeasureBench(false, 2, 5);)
+DEF_BENCH(return new MeasureBench(false, 10, 10);)
+DEF_BENCH(return new MeasureBench(false, 10, 20);)
+DEF_BENCH(return new MeasureBench(false, 10, 30);)
+DEF_BENCH(return new MeasureBench(false, 10, 40);)
+DEF_BENCH(return new MeasureBench(false, 10, 50);)
+DEF_BENCH(return new MeasureBench(false, 100, 100);)
+DEF_BENCH(return new MeasureBench(false, 100, 200);)
+DEF_BENCH(return new MeasureBench(false, 100, 300);)
+DEF_BENCH(return new MeasureBench(false, 100, 400);)
+DEF_BENCH(return new MeasureBench(false, 100, 500);)
+DEF_BENCH(return new MeasureBench(false, 1000, 1000);)
+DEF_BENCH(return new MeasureBench(false, 1000, 2000);)
+DEF_BENCH(return new MeasureBench(false, 1000, 3000);)
+DEF_BENCH(return new MeasureBench(false, 1000, 4000);)
+DEF_BENCH(return new MeasureBench(false, 1000, 5000);)
index f23203c..bc7001a 100644 (file)
@@ -42,6 +42,8 @@
         '<(skia_src_path)/utils/SkCanvasStack.h',
         '<(skia_src_path)/utils/SkCanvasStack.cpp',
         '<(skia_src_path)/utils/SkCanvasStateUtils.cpp',
+        '<(skia_src_path)/utils/SkCurveMeasure.cpp',
+        '<(skia_src_path)/utils/SkCurveMeasure.h',
         '<(skia_src_path)/utils/SkDashPath.cpp',
         '<(skia_src_path)/utils/SkDashPathPriv.h',
         '<(skia_src_path)/utils/SkDeferredCanvas.cpp',
diff --git a/src/utils/SkCurveMeasure.cpp b/src/utils/SkCurveMeasure.cpp
new file mode 100644 (file)
index 0000000..785bca7
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCurveMeasure.h"
+
+// for abs
+#include <cmath>
+
+static inline Sk8f evaluateDerivativeLength(const Sk8f& ts,
+                                            const Sk8f (&xCoeff)[3],
+                                            const Sk8f (&yCoeff)[3],
+                                            const SkSegType segType) {
+    Sk8f x;
+    Sk8f y;
+    switch (segType) {
+        case kQuad_SegType:
+            x = xCoeff[0]*ts + xCoeff[1];
+            y = yCoeff[0]*ts + yCoeff[1];
+            break;
+        case kLine_SegType:
+            SkDebugf("Unimplemented");
+            break;
+        case kCubic_SegType:
+            x = (xCoeff[0]*ts + xCoeff[1])*ts + xCoeff[2];
+            y = (yCoeff[0]*ts + yCoeff[1])*ts + yCoeff[2];
+            break;
+        case kConic_SegType:
+            SkDebugf("Unimplemented");
+            break;
+        default:
+            SkDebugf("Unimplemented");
+    }
+
+    x = x * x;
+    y = y * y;
+
+    return (x + y).sqrt();
+}
+ArcLengthIntegrator::ArcLengthIntegrator(const SkPoint* pts, SkSegType segType)
+    : fSegType(segType) {
+    switch (fSegType) {
+        case kQuad_SegType: {
+            float Ax = pts[0].x();
+            float Bx = pts[1].x();
+            float Cx = pts[2].x();
+            float Ay = pts[0].y();
+            float By = pts[1].y();
+            float Cy = pts[2].y();
+
+            // precompute coefficients for derivative
+            xCoeff[0] = Sk8f(2.0f*(Ax - 2*Bx + Cx));
+            xCoeff[1] = Sk8f(2.0f*(Bx - Ax));
+
+            yCoeff[0] = Sk8f(2.0f*(Ay - 2*By + Cy));
+            yCoeff[1] = Sk8f(2.0f*(By - Ay));
+        }
+            break;
+        case kLine_SegType:
+            SkDEBUGF(("Unimplemented"));
+            break;
+        case kCubic_SegType:
+        {
+            float Ax = pts[0].x();
+            float Bx = pts[1].x();
+            float Cx = pts[2].x();
+            float Dx = pts[3].x();
+            float Ay = pts[0].y();
+            float By = pts[1].y();
+            float Cy = pts[2].y();
+            float Dy = pts[3].y();
+
+            xCoeff[0] = Sk8f(3.0f*(-Ax + 3.0f*(Bx - Cx) + Dx));
+            xCoeff[1] = Sk8f(3.0f*(2.0f*(Ax - 2.0f*Bx + Cx)));
+            xCoeff[2] = Sk8f(3.0f*(-Ax + Bx));
+
+            yCoeff[0] = Sk8f(3.0f*(-Ay + 3.0f*(By - Cy) + Dy));
+            yCoeff[1] = Sk8f(3.0f * -Ay + By + 2.0f*(Ay - 2.0f*By + Cy));
+            yCoeff[2] = Sk8f(3.0f*(-Ay + By));
+        }
+            break;
+        case kConic_SegType:
+            SkDEBUGF(("Unimplemented"));
+            break;
+        default:
+            SkDEBUGF(("Unimplemented"));
+    }
+}
+
+// We use Gaussian quadrature
+// (https://en.wikipedia.org/wiki/Gaussian_quadrature)
+// to approximate the arc length integral here, because it is amenable to SIMD.
+SkScalar ArcLengthIntegrator::computeLength(SkScalar t) {
+    SkScalar length = 0.0f;
+
+    Sk8f lengths = evaluateDerivativeLength(absc*t, xCoeff, yCoeff, fSegType);
+    lengths = weights*lengths;
+    // is it faster or more accurate to sum and then multiply or vice versa?
+    lengths = lengths*(t*0.5f);
+
+    // Why does SkNx index with ints? does negative index mean something?
+    for (int i = 0; i < 8; i++) {
+        length += lengths[i];
+    }
+    return length;
+}
+
+SkCurveMeasure::SkCurveMeasure(const SkPoint* pts, SkSegType segType)
+    : fSegType(segType) {
+    switch (fSegType) {
+        case SkSegType::kQuad_SegType:
+            for (size_t i = 0; i < 3; i++) {
+                fPts[i] = pts[i];
+            }
+            break;
+        case SkSegType::kLine_SegType:
+            SkDebugf("Unimplemented");
+            break;
+        case SkSegType::kCubic_SegType:
+            for (size_t i = 0; i < 4; i++) {
+                fPts[i] = pts[i];
+            }
+            break;
+        case SkSegType::kConic_SegType:
+            SkDebugf("Unimplemented");
+            break;
+        default:
+            SkDEBUGF(("Unimplemented"));
+            break;
+    }
+    fIntegrator = ArcLengthIntegrator(fPts, fSegType);
+}
+
+SkScalar SkCurveMeasure::getLength() {
+    if (-1.0f == fLength) {
+        fLength = fIntegrator.computeLength(1.0f);
+    }
+    return fLength;
+}
+
+// Given an arc length targetLength, we want to determine what t
+// gives us the corresponding arc length along the curve.
+// We do this by letting the arc length integral := f(t) and
+// solving for the root of the equation f(t) - targetLength = 0
+// using Newton's method and lerp-bisection.
+// The computationally expensive parts are the integral approximation
+// at each step, and computing the derivative of the arc length integral,
+// which is equal to the length of the tangent (so we have to do a sqrt).
+
+SkScalar SkCurveMeasure::getTime(SkScalar targetLength) {
+    if (targetLength == 0.0f) {
+        return 0.0f;
+    }
+
+    SkScalar currentLength = getLength();
+
+    if (SkScalarNearlyEqual(targetLength, currentLength)) {
+        return 1.0f;
+    }
+
+    // initial estimate of t is percentage of total length
+    SkScalar currentT = targetLength / currentLength;
+    SkScalar prevT = -1.0f;
+    SkScalar newT;
+
+    SkScalar minT = 0.0f;
+    SkScalar maxT = 1.0f;
+
+    int iterations = 0;
+    while (iterations < kNewtonIters + kBisectIters) {
+        currentLength = fIntegrator.computeLength(currentT);
+        SkScalar lengthDiff = currentLength - targetLength;
+
+        // Update root bounds.
+        // If lengthDiff is positive, we have overshot the target, so
+        // we know the current t is an upper bound, and similarly
+        // for the lower bound.
+        if (lengthDiff > 0.0f) {
+            if (currentT < maxT) {
+                maxT = currentT;
+            }
+        } else {
+            if (currentT > minT) {
+                minT = currentT;
+            }
+        }
+
+        // We have a tolerance on both the absolute value of the difference and
+        // on the t value
+        // because we may not have enough precision in the t to get close enough
+        // in the length.
+        if ((std::abs(lengthDiff) < kTolerance) ||
+            (std::abs(prevT - currentT) < kTolerance)) {
+            break;
+        }
+
+        prevT = currentT;
+        if (iterations < kNewtonIters) {
+            // TODO(hstern) switch here on curve type.
+            // This is just newton's formula.
+            SkScalar dt = evaluateQuadDerivative(currentT).length();
+            newT = currentT - (lengthDiff / dt);
+
+            // If newT is out of bounds, bisect inside newton.
+            if ((newT < 0.0f) || (newT > 1.0f)) {
+                newT = (minT + maxT) * 0.5f;
+            }
+        } else if (iterations < kNewtonIters + kBisectIters) {
+            if (lengthDiff > 0.0f) {
+                maxT = currentT;
+            } else {
+                minT = currentT;
+            }
+            // TODO(hstern) do a lerp here instead of a bisection
+            newT = (minT + maxT) * 0.5f;
+        } else {
+            SkDEBUGF(("%.7f %.7f didn't get close enough after bisection.\n",
+                     currentT, currentLength));
+            break;
+        }
+        currentT = newT;
+
+        SkASSERT(minT <= maxT);
+
+        iterations++;
+    }
+
+    // debug. is there an SKDEBUG or something for ifdefs?
+    fIters = iterations;
+
+    return currentT;
+}
+
+void SkCurveMeasure::getPosTan(SkScalar targetLength, SkPoint* pos,
+                               SkVector* tan) {
+    SkScalar t = getTime(targetLength);
+
+    if (pos) {
+        // TODO(hstern) switch here on curve type.
+        *pos = evaluateQuad(t);
+    }
+    if (tan) {
+        // TODO(hstern) switch here on curve type.
+        *tan = evaluateQuadDerivative(t);
+    }
+}
+
+// this is why I feel that the ArcLengthIntegrator should be combined
+// with some sort of evaluator that caches the constants computed from the
+// control points. this is basically the same code in ArcLengthIntegrator
+SkPoint SkCurveMeasure::evaluateQuad(SkScalar t) {
+    SkScalar ti = 1.0f - t;
+
+    SkScalar Ax = fPts[0].x();
+    SkScalar Bx = fPts[1].x();
+    SkScalar Cx = fPts[2].x();
+    SkScalar Ay = fPts[0].y();
+    SkScalar By = fPts[1].y();
+    SkScalar Cy = fPts[2].y();
+
+    SkScalar x = Ax*ti*ti + 2.0f*Bx*t*ti + Cx*t*t;
+    SkScalar y = Ay*ti*ti + 2.0f*By*t*ti + Cy*t*t;
+    return SkPoint::Make(x, y);
+}
+
+SkVector SkCurveMeasure::evaluateQuadDerivative(SkScalar t) {
+    SkScalar Ax = fPts[0].x();
+    SkScalar Bx = fPts[1].x();
+    SkScalar Cx = fPts[2].x();
+    SkScalar Ay = fPts[0].y();
+    SkScalar By = fPts[1].y();
+    SkScalar Cy = fPts[2].y();
+
+    SkScalar A2BCx = 2.0f*(Ax - 2*Bx + Cx);
+    SkScalar A2BCy = 2.0f*(Ay - 2*By + Cy);
+    SkScalar ABx = 2.0f*(Bx - Ax);
+    SkScalar ABy = 2.0f*(By - Ay);
+
+    return SkPoint::Make(A2BCx*t + ABx, A2BCy*t + ABy);
+}
diff --git a/src/utils/SkCurveMeasure.h b/src/utils/SkCurveMeasure.h
new file mode 100644 (file)
index 0000000..60de29e
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCurveMeasure_DEFINED
+#define SkCurveMeasure_DEFINED
+
+#include "SkPoint.h"
+#include "SkNx.h"
+
+// These are weights and abscissae for gaussian quadrature with weight function
+// w(x) = 1
+static SkScalar weights8[8] = {0.3626837833783620f, 0.3626837833783620f,
+                               0.3137066458778873f, 0.3137066458778873f,
+                               0.2223810344533745f, 0.2223810344533745f,
+                               0.1012285362903763f, 0.1012285362903763f};
+static SkScalar absc8[8] = {-0.1834346424956498f, 0.1834346424956498f,
+                            -0.5255324099163290f, 0.5255324099163290f,
+                            -0.7966664774136267f, 0.7966664774136267f,
+                            -0.9602898564975363f, 0.9602898564975363f};
+
+static Sk8f weights = Sk8f::Load(weights8);
+static Sk8f absc = 0.5f*(Sk8f::Load(absc8) + 1.0f);
+
+
+// this is the same enum as in SkPathMeasure but it doesn't have a name there
+enum SkSegType {
+    kLine_SegType,
+    kQuad_SegType,
+    kCubic_SegType,
+    kConic_SegType,
+};
+
+class ArcLengthIntegrator {
+public:
+    ArcLengthIntegrator() {}
+    ArcLengthIntegrator(const SkPoint* pts, SkSegType segType);
+    SkScalar computeLength(SkScalar t);
+
+private:
+    SkSegType fSegType;
+
+    // precomputed coefficients for derivatives in Horner form
+    Sk8f xCoeff[3];
+    Sk8f yCoeff[3];
+};
+
+class SkCurveMeasure {
+public:
+    SkCurveMeasure() {}
+    SkCurveMeasure(const SkPoint* pts, SkSegType segType);
+
+    SkScalar getTime(SkScalar targetLength);
+    void getPosTan(SkScalar distance, SkPoint* pos, SkVector* tan);
+    SkScalar getLength();
+
+private:
+    SkPoint evaluateQuad(SkScalar t);
+    SkVector evaluateQuadDerivative(SkScalar t);
+    //SkPoint evaluate_cubic(SkScalar t);
+    //SkVector evaluate_cubic_derivative(SkScalar t);
+    //SkPoint evaluate_conic(SkScalar t);
+    //SkVector evaluate_conic_derivative(SkScalar t);
+
+    const SkScalar kTolerance = 0.0001f;
+    const int kNewtonIters = 5;
+    const int kBisectIters = 5;
+
+    SkSegType fSegType;
+    SkPoint fPts[4];
+    SkScalar fLength = -1.0f;
+    ArcLengthIntegrator fIntegrator;
+
+    // for debug purposes
+    int fIters;
+};
+
+#endif  // SkCurveMeasure_DEFINED