diff options
author | reed <reed@google.com> | 2015-03-20 13:23:43 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-20 13:23:43 -0700 |
commit | b640203cd5733aaf110277e28e22007c5b541565 (patch) | |
tree | e04949bb246b644564bba2e8f304373967931f5c | |
parent | cea9f35fee49866410c6e0b9b9256df27961f495 (diff) |
use Sk2s for conics
BUG=skia:
Review URL: https://codereview.chromium.org/1025033002
-rw-r--r-- | bench/PathBench.cpp | 80 | ||||
-rw-r--r-- | src/core/SkGeometry.cpp | 161 | ||||
-rw-r--r-- | src/core/SkGeometry.h | 4 | ||||
-rw-r--r-- | tests/GeometryTest.cpp | 53 |
4 files changed, 263 insertions, 35 deletions
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp index b597f919f8..3a4eb63830 100644 --- a/bench/PathBench.cpp +++ b/bench/PathBench.cpp @@ -832,29 +832,91 @@ private: }; class ConicBench_ChopHalf : public Benchmark { - SkConic fRQ; +protected: + SkConic fRQ, fDst[2]; + SkString fName; + const bool fUseV2; public: - ConicBench_ChopHalf() { + ConicBench_ChopHalf(bool useV2) : fUseV2(useV2) { fRQ.fPts[0].set(0, 0); fRQ.fPts[1].set(100, 0); fRQ.fPts[2].set(100, 100); fRQ.fW = SkScalarCos(SK_ScalarPI/4); + + fName.printf("conic-chop-half%d", useV2); } -private: - const char* onGetName() SK_OVERRIDE { - return "ratquad-chop-half"; + bool isSuitableFor(Backend backend) SK_OVERRIDE { + return backend == kNonRendering_Backend; } +private: + const char* onGetName() SK_OVERRIDE { return fName.c_str(); } + void onDraw(const int loops, SkCanvas*) SK_OVERRIDE { - SkConic dst[2]; - for (int i = 0; i < loops; ++i) { - fRQ.chop(dst); + if (fUseV2) { + for (int i = 0; i < loops; ++i) { + fRQ.chop2(fDst); + } + } else { + for (int i = 0; i < loops; ++i) { + fRQ.chop(fDst); + } } } typedef Benchmark INHERITED; }; +DEF_BENCH( return new ConicBench_ChopHalf(false); ) +DEF_BENCH( return new ConicBench_ChopHalf(true); ) + +class ConicBench_EvalPos : public ConicBench_ChopHalf { +public: + ConicBench_EvalPos(bool useV2) : ConicBench_ChopHalf(useV2) { + fName.printf("conic-eval-pos%d", useV2); + } + void onDraw(const int loops, SkCanvas*) SK_OVERRIDE { + if (fUseV2) { + for (int i = 0; i < loops; ++i) { + for (int j = 0; j < 1000; ++j) { + fDst[0].fPts[0] = fRQ.evalAt(0.4f); + } + } + } else { + for (int i = 0; i < loops; ++i) { + for (int j = 0; j < 1000; ++j) { + fRQ.evalAt(0.4f, &fDst[0].fPts[0], NULL); + } + } + } + } +}; +DEF_BENCH( return new ConicBench_EvalPos(false); ) +DEF_BENCH( return new ConicBench_EvalPos(true); ) + +class ConicBench_EvalTan : public ConicBench_ChopHalf { +public: + ConicBench_EvalTan(bool useV2) : ConicBench_ChopHalf(useV2) { + fName.printf("conic-eval-tan%d", useV2); + } + void onDraw(const int loops, SkCanvas*) SK_OVERRIDE { + if (fUseV2) { + for (int i = 0; i < loops; ++i) { + for (int j = 0; j < 1000; ++j) { + fDst[0].fPts[0] = fRQ.evalTangentAt(0.4f); + } + } + } else { + for (int i = 0; i < loops; ++i) { + for (int j = 0; j < 1000; ++j) { + fRQ.evalAt(0.4f, NULL, &fDst[0].fPts[0]); + } + } + } + } +}; +DEF_BENCH( return new ConicBench_EvalTan(false); ) +DEF_BENCH( return new ConicBench_EvalTan(true); ) /////////////////////////////////////////////////////////////////////////////// @@ -1012,10 +1074,10 @@ DEF_BENCH( return new ConservativelyContainsBench(ConservativelyContainsBench::k DEF_BENCH( return new ConservativelyContainsBench(ConservativelyContainsBench::kRoundRect_Type); ) DEF_BENCH( return new ConservativelyContainsBench(ConservativelyContainsBench::kOval_Type); ) + // These seem to be optimized away, which is troublesome for timing. /* DEF_BENCH( return new ConicBench_Chop5() ) -DEF_BENCH( return new ConicBench_ChopHalf() ) DEF_BENCH( return new ConicBench_ComputeError() ) DEF_BENCH( return new ConicBench_asQuadTol() ) DEF_BENCH( return new ConicBench_quadPow2() ) diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp index aea1fe5665..284f07e285 100644 --- a/src/core/SkGeometry.cpp +++ b/src/core/SkGeometry.cpp @@ -9,6 +9,33 @@ #include "SkMatrix.h" #include "Sk2x.h" +static Sk2s from_point(const SkPoint& point) { + return Sk2s::Load(&point.fX); +} + +static SkPoint to_point(const Sk2s& x) { + SkPoint point; + x.store(&point.fX); + return point; +} + +static SkVector to_vector(const Sk2s& x) { + SkVector vector; + x.store(&vector.fX); + return vector; +} + +#if 0 +static Sk2s divide(const Sk2s& numer, const Sk2s& denom) { + SkScalar numerStorage[2], denomStorage[2]; + numer.store(numerStorage); + denom.store(denomStorage); + numerStorage[0] /= denomStorage[0]; + numerStorage[1] /= denomStorage[1]; + return Sk2s::Load(numerStorage); +} +#endif + /** If defined, this makes eval_quad and eval_cubic do more setup (sometimes involving integer multiplies by 2 or 3, but fewer calls to SkScalarMul. May also introduce overflow of fixed when we compute our setup. @@ -92,6 +119,10 @@ int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2]) { /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// +static Sk2s quad_poly_eval(const Sk2s& A, const Sk2s& B, const Sk2s& C, const Sk2s& t) { + return (A * t + B) * t + C; +} + static SkScalar eval_quad(const SkScalar src[], SkScalar t) { SkASSERT(src); SkASSERT(t >= 0 && t <= SK_Scalar1); @@ -132,35 +163,31 @@ SkPoint SkEvalQuadAt(const SkPoint src[3], SkScalar t) { SkASSERT(src); SkASSERT(t >= 0 && t <= SK_Scalar1); - const Sk2f t2(t); + const Sk2s t2(t); - Sk2f P0 = Sk2f::Load(&src[0].fX); - Sk2f P1 = Sk2f::Load(&src[1].fX); - Sk2f P2 = Sk2f::Load(&src[2].fX); + Sk2s P0 = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); - Sk2f B = P1 - P0; - Sk2f A = P2 - P1 - B; + Sk2s B = P1 - P0; + Sk2s A = P2 - P1 - B; - SkPoint result; - ((A * t2 + B+B) * t2 + P0).store(&result.fX); - return result; + return to_point((A * t2 + B+B) * t2 + P0); } SkVector SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t) { SkASSERT(src); SkASSERT(t >= 0 && t <= SK_Scalar1); - Sk2f P0 = Sk2f::Load(&src[0].fX); - Sk2f P1 = Sk2f::Load(&src[1].fX); - Sk2f P2 = Sk2f::Load(&src[2].fX); + Sk2s P0 = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); - Sk2f B = P1 - P0; - Sk2f A = P2 - P1 - B; - Sk2f T = A * Sk2f(t) + B; + Sk2s B = P1 - P0; + Sk2s A = P2 - P1 - B; + Sk2s T = A * Sk2s(t) + B; - SkVector result; - (T + T).store(&result.fX); - return result; + return to_vector(T + T); } static void interp_quad_coords(const SkScalar* src, SkScalar* dst, SkScalar t) { @@ -188,19 +215,19 @@ static inline Sk2s interp(const Sk2s& v0, const Sk2s& v1, const Sk2s& t) { void SkChopQuadAt2(const SkPoint src[3], SkPoint dst[5], SkScalar t) { SkASSERT(t > 0 && t < SK_Scalar1); - Sk2s p0 = Sk2f::Load(&src[0].fX); - Sk2s p1 = Sk2f::Load(&src[1].fX); - Sk2s p2 = Sk2f::Load(&src[2].fX); + Sk2s p0 = from_point(src[0]); + Sk2s p1 = from_point(src[1]); + Sk2s p2 = from_point(src[2]); Sk2s tt = Sk2s(t); Sk2s p01 = interp(p0, p1, tt); Sk2s p12 = interp(p1, p2, tt); - p0.store(&dst[0].fX); - p01.store(&dst[1].fX); - interp(p01, p12, tt).store(&dst[2].fX); - p12.store(&dst[3].fX); - p2.store(&dst[4].fX); + dst[0] = to_point(p0); + dst[1] = to_point(p01); + dst[2] = to_point(interp(p01, p12, tt)); + dst[3] = to_point(p12); + dst[4] = to_point(p2); } void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) { @@ -1251,6 +1278,65 @@ void SkConic::chopAt(SkScalar t, SkConic dst[2]) const { dst[1].fW = tmp2[2].fZ / root; } +static Sk2s times_2(const Sk2s& value) { + return value + value; +} + +SkPoint SkConic::evalAt(SkScalar t) const { + Sk2s p0 = from_point(fPts[0]); + Sk2s p1 = from_point(fPts[1]); + Sk2s p2 = from_point(fPts[2]); + Sk2s tt = Sk2s(t); + Sk2s ww = Sk2s(fW); + Sk2s one = Sk2s(1); + + Sk2s p1w = p1 * ww; + Sk2s C = p0; + Sk2s A = p2 - times_2(p1w) + p0; + Sk2s B = times_2(p1w - C); + Sk2s numer = quad_poly_eval(A, B, C, tt); + + B = times_2(ww - one); + A = -B; + Sk2s denom = quad_poly_eval(A, B, one, tt); + + return to_point(numer / denom); +} + +SkVector SkConic::evalTangentAt(SkScalar t) const { + Sk2s p0 = from_point(fPts[0]); + Sk2s p1 = from_point(fPts[1]); + Sk2s p2 = from_point(fPts[2]); + Sk2s ww = Sk2s(fW); + + Sk2s p20 = p2 - p0; + Sk2s p10 = p1 - p0; + + Sk2s C = ww * p10; + Sk2s A = ww * p20 - p20; + Sk2s B = p20 - C - C; + + return to_vector(quad_poly_eval(A, B, C, Sk2s(t))); +#if 0 + static void conic_deriv_coeff(const SkScalar src[], + SkScalar w, + SkScalar coeff[3]) { + const SkScalar P20 = src[4] - src[0]; + const SkScalar P10 = src[2] - src[0]; + const SkScalar wP10 = w * P10; + coeff[0] = w * P20 - P20; + coeff[1] = P20 - 2 * wP10; + coeff[2] = wP10; + } + + static SkScalar conic_eval_tan(const SkScalar coord[], SkScalar w, SkScalar t) { + SkScalar coeff[3]; + conic_deriv_coeff(coord, w, coeff); + return t * (t * coeff[0] + coeff[1]) + coeff[2]; + } +#endif +} + static SkScalar subdivide_w_value(SkScalar w) { return SkScalarSqrt(SK_ScalarHalf + w * SK_ScalarHalf); } @@ -1275,6 +1361,29 @@ void SkConic::chop(SkConic dst[2]) const { dst[0].fW = dst[1].fW = subdivide_w_value(fW); } +void SkConic::chop2(SkConic * SK_RESTRICT dst) const { + Sk2s scale(SkScalarInvert(SK_Scalar1 + fW)); +// Sk2s scale = Sk2s(SK_Scalar1 + fW).invert(); + SkScalar newW = subdivide_w_value(fW); + + Sk2s p0 = from_point(fPts[0]); + Sk2s p1 = from_point(fPts[1]); + Sk2s p2 = from_point(fPts[2]); + Sk2s ww = Sk2s(fW); + Sk2s half = Sk2s(0.5f); + + Sk2s wp1 = ww * p1; + Sk2s m = ((p0 + wp1 + wp1 + p2) * half) * scale; + + dst[0].fPts[0] = fPts[0]; + dst[0].fPts[1] = to_point((p0 + wp1) * scale); + dst[0].fPts[2] = dst[1].fPts[0] = to_point(m); + dst[1].fPts[1] = to_point((wp1 + p2) * scale); + dst[1].fPts[2] = fPts[2]; + + dst[0].fW = dst[1].fW = newW; +} + /* * "High order approximation of conic sections by quadratic splines" * by Michael Floater, 1993 diff --git a/src/core/SkGeometry.h b/src/core/SkGeometry.h index 5f7aa5e7b3..5a6dcb5d37 100644 --- a/src/core/SkGeometry.h +++ b/src/core/SkGeometry.h @@ -219,6 +219,10 @@ struct SkConic { void chopAt(SkScalar t, SkConic dst[2]) const; void chop(SkConic dst[2]) const; + SkPoint evalAt(SkScalar t) const; + SkVector evalTangentAt(SkScalar t) const; + void chop2(SkConic dst[2]) const; + void computeAsQuadError(SkVector* err) const; bool asQuadTol(SkScalar tol) const; diff --git a/tests/GeometryTest.cpp b/tests/GeometryTest.cpp index aba8dd224b..8a7bfe3b49 100644 --- a/tests/GeometryTest.cpp +++ b/tests/GeometryTest.cpp @@ -77,6 +77,58 @@ static void test_evalquadat(skiatest::Reporter* reporter) { } } +static void test_conic_eval_pos(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) { + SkPoint p0, p1; + conic.evalAt(t, &p0, NULL); + p1 = conic.evalAt(t); + check_pairs(reporter, 0, t, "conic-pos", p0.fX, p0.fY, p1.fX, p1.fY); +} + +static void test_conic_eval_tan(skiatest::Reporter* reporter, const SkConic& conic, SkScalar t) { + SkVector v0, v1; + conic.evalAt(t, NULL, &v0); + v1 = conic.evalTangentAt(t); + check_pairs(reporter, 0, t, "conic-tan", v0.fX, v0.fY, v1.fX, v1.fY); +} + +static void test_conic_chop_half(skiatest::Reporter* reporter, const SkConic& conic) { + SkConic dst0[2], dst1[2]; + conic.chop(dst0); + conic.chop2(dst1); + + for (int i = 0; i < 2; ++i) { + REPORTER_ASSERT(reporter, dst0[i].fW == dst1[i].fW); + for (int j = 0; j < 3; ++j) { + check_pairs(reporter, j, 0.5f, "conic-chop", + dst0[i].fPts[j].fX, dst0[i].fPts[j].fY, + dst0[i].fPts[j].fX, dst1[i].fPts[j].fY); + } + } +} + +static void test_conic(skiatest::Reporter* reporter) { + SkRandom rand; + for (int i = 0; i < 1000; ++i) { + SkPoint pts[3]; + for (int j = 0; j < 3; ++j) { + pts[j].set(rand.nextSScalar1() * 100, rand.nextSScalar1() * 100); + } + for (int k = 0; k < 10; ++k) { + SkScalar w = rand.nextUScalar1() * 2; + SkConic conic(pts, w); + test_conic_chop_half(reporter, conic); + + const SkScalar dt = SK_Scalar1 / 128; + SkScalar t = dt; + for (int j = 1; j < 128; ++j) { + test_conic_eval_pos(reporter, conic, t); + test_conic_eval_tan(reporter, conic, t); + t += dt; + } + } + } +} + DEF_TEST(Geometry, reporter) { SkPoint pts[3], dst[5]; @@ -103,4 +155,5 @@ DEF_TEST(Geometry, reporter) { testChopCubic(reporter); test_evalquadat(reporter); + test_conic(reporter); } |