From 419a94da028b33425a0feeb44d0d022a5d3d3704 Mon Sep 17 00:00:00 2001 From: Chris Dalton Date: Mon, 28 Aug 2017 10:24:22 -0600 Subject: [PATCH] Add a GrCCPRGeometry file Enough ccpr-specific geometry code is in flight that it feels like it should have its own file. Bug: skia: Change-Id: I99ef620a7dc35178cf774b3a4ec6159d46f401c7 Reviewed-on: https://skia-review.googlesource.com/39162 Commit-Queue: Chris Dalton Reviewed-by: Brian Salomon --- gn/gpu.gni | 2 + samplecode/SampleCCPRGeometry.cpp | 11 ++- src/gpu/GrPathUtils.cpp | 60 --------------- src/gpu/GrPathUtils.h | 8 -- src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp | 5 +- src/gpu/ccpr/GrCCPRGeometry.cpp | 94 +++++++++++++++++++++++ src/gpu/ccpr/GrCCPRGeometry.h | 29 +++++++ 7 files changed, 132 insertions(+), 77 deletions(-) create mode 100644 src/gpu/ccpr/GrCCPRGeometry.cpp create mode 100644 src/gpu/ccpr/GrCCPRGeometry.h diff --git a/gn/gpu.gni b/gn/gpu.gni index 51ac125366..3853b4ed0a 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -295,6 +295,8 @@ skia_gpu_sources = [ "$_src/gpu/ccpr/GrCCPRCoverageProcessor.h", "$_src/gpu/ccpr/GrCCPRCubicProcessor.cpp", "$_src/gpu/ccpr/GrCCPRCubicProcessor.h", + "$_src/gpu/ccpr/GrCCPRGeometry.cpp", + "$_src/gpu/ccpr/GrCCPRGeometry.h", "$_src/gpu/ccpr/GrCCPRPathProcessor.cpp", "$_src/gpu/ccpr/GrCCPRPathProcessor.h", "$_src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp", diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp index 9749e45a29..b4bf2bd288 100644 --- a/samplecode/SampleCCPRGeometry.cpp +++ b/samplecode/SampleCCPRGeometry.cpp @@ -10,7 +10,6 @@ #if SK_SUPPORT_GPU #include "GrContextPriv.h" -#include "GrPathUtils.h" #include "GrRenderTargetContext.h" #include "GrRenderTargetContextPriv.h" #include "GrResourceProvider.h" @@ -22,6 +21,7 @@ #include "SkPath.h" #include "SkView.h" #include "ccpr/GrCCPRCoverageProcessor.h" +#include "ccpr/GrCCPRGeometry.h" #include "gl/GrGLGpu.cpp" #include "ops/GrDrawOp.h" @@ -202,10 +202,9 @@ void CCPRGeometryView::updateGpuData() { fGpuInstances.push_back().fCubicData = {controlPointsIdx + i * 4, i}; } } else if (is_curve(fMode)) { - SkPoint P[3] = {fPoints[0], fPoints[1], fPoints[3]}; SkPoint chopped[5]; - fGpuPoints.push_back(P[0]); - if (GrPathUtils::chopMonotonicQuads(P, chopped)) { + fGpuPoints.push_back(fPoints[0]); + if (GrCCPRChopMonotonicQuadratics(fPoints[0], fPoints[1], fPoints[3], chopped)) { // Endpoints. fGpuPoints.push_back(chopped[2]); fGpuPoints.push_back(chopped[4]); @@ -215,8 +214,8 @@ void CCPRGeometryView::updateGpuData() { fGpuInstances.push_back().fQuadraticData = {3, 0}; fGpuInstances.push_back().fQuadraticData = {4, 1}; } else { - fGpuPoints.push_back(P[2]); - fGpuPoints.push_back(P[1]); + fGpuPoints.push_back(fPoints[3]); + fGpuPoints.push_back(fPoints[1]); fGpuInstances.push_back().fQuadraticData = {2, 0}; } } else { diff --git a/src/gpu/GrPathUtils.cpp b/src/gpu/GrPathUtils.cpp index 0089fc1703..a8abf81a63 100644 --- a/src/gpu/GrPathUtils.cpp +++ b/src/gpu/GrPathUtils.cpp @@ -567,66 +567,6 @@ void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4], } } -static inline Sk2f normalize(const Sk2f& n) { - Sk2f nn = n*n; - return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt(); -} - -bool GrPathUtils::chopMonotonicQuads(const SkPoint p[3], SkPoint dst[5]) { - GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); - GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint)); - GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX)); - - Sk2f p0 = Sk2f::Load(&p[0]); - Sk2f p1 = Sk2f::Load(&p[1]); - Sk2f p2 = Sk2f::Load(&p[2]); - - Sk2f tan0 = p1 - p0; - Sk2f tan1 = p2 - p1; - Sk2f v = p2 - p0; - - // Check if the curve is already monotonic (i.e. (tan0 dot v) >= 0 and (tan1 dot v) >= 0). - // This should almost always be this case for well-behaved curves in the real world. - float dot0[2], dot1[2]; - (tan0 * v).store(dot0); - (tan1 * v).store(dot1); - if (dot0[0] + dot0[1] >= 0 && dot1[0] + dot1[1] >= 0) { - return false; - } - - // Chop the curve into two segments with equal curvature. To do this we find the T value whose - // tangent is perpendicular to the vector that bisects tan0 and -tan1. - Sk2f n = normalize(tan0) - normalize(tan1); - - // This tangent can be found where (dQ(t) dot n) = 0: - // - // 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n | - // | -2*p0 + 2*p1 | | . | - // - // = | 2*t 1 | * | tan1 - tan0 | * | n | - // | 2*tan0 | | . | - // - // = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n) - // - // t = (tan0 dot n) / ((tan0 - tan1) dot n) - Sk2f dQ1n = (tan0 - tan1) * n; - Sk2f dQ0n = tan0 * n; - Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n)); - t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error. - - Sk2f p01 = SkNx_fma(t, tan0, p0); - Sk2f p12 = SkNx_fma(t, tan1, p1); - Sk2f p012 = SkNx_fma(t, p12 - p01, p01); - - p0.store(&dst[0]); - p01.store(&dst[1]); - p012.store(&dst[2]); - p12.store(&dst[3]); - p2.store(&dst[4]); - - return true; -} - //////////////////////////////////////////////////////////////////////////////// /** diff --git a/src/gpu/GrPathUtils.h b/src/gpu/GrPathUtils.h index 4643bff716..e9dee7316a 100644 --- a/src/gpu/GrPathUtils.h +++ b/src/gpu/GrPathUtils.h @@ -124,14 +124,6 @@ namespace GrPathUtils { SkPathPriv::FirstDirection dir, SkTArray* quads); - // Ensures that a quadratic bezier is monotonic with respect to its vector [P2 - P0] (the vector - // between its endpoints). In the event that the curve is not monotonic, it is chopped into two - // segments that are monotonic. This should be rare for well-behaved curves in the real world. - // - // Returns false if the curve was already monotonic. - // true if it was chopped into two monotonic segments, now contained in dst. - bool chopMonotonicQuads(const SkPoint p[3], SkPoint dst[5]); - // Computes the KLM linear functionals for the cubic implicit form. The "right" side of the // curve (when facing in the direction of increasing parameter values) will be the area that // satisfies: diff --git a/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp b/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp index 361a15999d..f2d27e8269 100644 --- a/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp +++ b/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp @@ -11,7 +11,6 @@ #include "GrGpuCommandBuffer.h" #include "GrOnFlushResourceProvider.h" #include "GrOpFlushState.h" -#include "GrPathUtils.h" #include "SkGeometry.h" #include "SkMakeUnique.h" #include "SkMathPriv.h" @@ -19,6 +18,7 @@ #include "SkPathPriv.h" #include "SkPoint.h" #include "SkNx.h" +#include "ccpr/GrCCPRGeometry.h" #include "ops/GrDrawOp.h" #include "../pathops/SkPathOpsCubic.h" #include @@ -311,9 +311,8 @@ void GrCCPRCoverageOpsBuilder::fanTo(const SkPoint& pt) { void GrCCPRCoverageOpsBuilder::quadraticTo(SkPoint controlPt, SkPoint endPt) { SkASSERT(fCurrPathIndices.fQuadratics+2 <= fBaseInstances[(int)fCurrScissorMode].fSerpentines); - SkPoint P[3] = {fCurrFanPoint, controlPt, endPt}; SkPoint chopped[5]; - if (GrPathUtils::chopMonotonicQuads(P, chopped)) { + if (GrCCPRChopMonotonicQuadratics(fCurrFanPoint, controlPt, endPt, chopped)) { this->fanTo(chopped[2]); fPointsData[fControlPtsIdx++] = chopped[1]; fInstanceData[fCurrPathIndices.fQuadratics++].fQuadraticData = { diff --git a/src/gpu/ccpr/GrCCPRGeometry.cpp b/src/gpu/ccpr/GrCCPRGeometry.cpp new file mode 100644 index 0000000000..f756f6e785 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRGeometry.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrCCPRGeometry.h" + +#include "GrTypes.h" +#include "SkPoint.h" +#include "SkNx.h" +#include +#include +#include + +// We convert between SkPoint and Sk2f freely throughout this file. +GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); +GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint)); +GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX)); + +static inline Sk2f normalize(const Sk2f& n) { + Sk2f nn = n*n; + return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt(); +} + +static inline float dot(const Sk2f& a, const Sk2f& b) { + float product[2]; + (a * b).store(product); + return product[0] + product[1]; +} + +// Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt]. +static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& startTan, + const Sk2f& endPt, const Sk2f& endTan) { + Sk2f v = endPt - startPt; + float dot0 = dot(startTan, v); + float dot1 = dot(endTan, v); + + // A small, negative tolerance handles floating-point error in the case when one tangent + // approaches 0 length, meaning the (convex) curve segment is effectively a flat line. + float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero; + return dot0 >= tolerance && dot1 >= tolerance; +} + +static inline Sk2f lerp(const Sk2f& a, const Sk2f& b, const Sk2f& t) { + return SkNx_fma(t, b - a, a); +} + +bool GrCCPRChopMonotonicQuadratics(const SkPoint& startPt, const SkPoint& controlPt, + const SkPoint& endPt, SkPoint dst[5]) { + Sk2f p0 = Sk2f::Load(&startPt); + Sk2f p1 = Sk2f::Load(&controlPt); + Sk2f p2 = Sk2f::Load(&endPt); + + Sk2f tan0 = p1 - p0; + Sk2f tan1 = p2 - p1; + // This should almost always be this case for well-behaved curves in the real world. + if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) { + return false; + } + + // Chop the curve into two segments with equal curvature. To do this we find the T value whose + // tangent is perpendicular to the vector that bisects tan0 and -tan1. + Sk2f n = normalize(tan0) - normalize(tan1); + + // This tangent can be found where (dQ(t) dot n) = 0: + // + // 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n | + // | -2*p0 + 2*p1 | | . | + // + // = | 2*t 1 | * | tan1 - tan0 | * | n | + // | 2*tan0 | | . | + // + // = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n) + // + // t = (tan0 dot n) / ((tan0 - tan1) dot n) + Sk2f dQ1n = (tan0 - tan1) * n; + Sk2f dQ0n = tan0 * n; + Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n)); + t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error. + + Sk2f p01 = SkNx_fma(t, tan0, p0); + Sk2f p12 = SkNx_fma(t, tan1, p1); + Sk2f p012 = lerp(p01, p12, t); + + p0.store(&dst[0]); + p01.store(&dst[1]); + p012.store(&dst[2]); + p12.store(&dst[3]); + p2.store(&dst[4]); + + return true; +} diff --git a/src/gpu/ccpr/GrCCPRGeometry.h b/src/gpu/ccpr/GrCCPRGeometry.h new file mode 100644 index 0000000000..cb8bb3a8d7 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRGeometry.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGrCCPRGeometry_DEFINED +#define GrGrCCPRGeometry_DEFINED + +#include "SkTypes.h" + +struct SkPoint; + +/* + * Ensures that a quadratic bezier is monotonic with respect to the vector between its endpoints + * [P2 - P0]. In the event that the curve is not monotonic, it is chopped into two segments that + * are. This should be rare for well-behaved curves in the real world. + * + * NOTE: This must be done in device space, since an affine transformation can change whether a + * curve is monotonic. + * + * Returns false if the curve was already monotonic. + * true if it was chopped into two monotonic segments, now contained in dst. + */ +bool GrCCPRChopMonotonicQuadratics(const SkPoint& startPt, const SkPoint& controlPt, + const SkPoint& endPt, SkPoint dst[5]); + +#endif