Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Enumerations | Functions | Variables
skgpu::tess Namespace Reference

Classes

struct  AddTrianglesWhenChopping
 
class  AffineMatrix
 
struct  AttribValue
 
class  CullTest
 
struct  DiscardFlatCurves
 
class  FixedCountCurves
 
class  FixedCountStrokes
 
class  FixedCountWedges
 
class  LinearTolerances
 
class  MiddleOutPolygonTriangulator
 
class  MidpointContourParser
 
struct  NullTriangulator
 
struct  Optional
 
struct  PatchStorage
 
class  PatchWriter
 
class  PathMiddleOutFanIter
 
struct  ReplicateLineEndPoints
 
struct  Required
 
class  StrokeIterator
 
struct  StrokeParams
 
struct  TrackJoinControlPoints
 

Enumerations

enum class  PatchAttribs {
  kNone = 0 , kJoinControlPoint = 1 << 0 , kFanPoint = 1 << 1 , kStrokeParams = 1 << 2 ,
  kColor = 1 << 3 , kPaintDepth = 1 << 4 , kExplicitCurveType = 1 << 5 , kSsboIndex = 1 << 7 ,
  kWideColorIfEnabled = 1 << 6
}
 

Functions

template<PatchAttribs A, typename T , bool Required, bool Optional>
VertexWriteroperator<< (VertexWriter &w, const AttribValue< A, T, Required, Optional > &v)
 
SkPath PreChopPathCurves (float tessellationPrecision, const SkPath &path, const SkMatrix &matrix, const SkRect &viewport)
 
int FindCubicConvex180Chops (const SkPoint pts[], float T[2], bool *areCusps)
 
static constexpr int NumCurveTrianglesAtResolveLevel (int resolveLevel)
 
constexpr size_t PatchAttribsStride (PatchAttribs attribs)
 
constexpr size_t PatchStride (PatchAttribs attribs)
 
bool ConicHasCusp (const SkPoint p[3])
 
float GetJoinType (const SkStrokeRec &stroke)
 
bool StrokesHaveEqualParams (const SkStrokeRec &a, const SkStrokeRec &b)
 
constexpr int NumFixedEdgesInJoin (SkPaint::Join joinType)
 
constexpr int NumFixedEdgesInJoin (const StrokeParams &strokeParams)
 
float CalcNumRadialSegmentsPerRadian (float approxDevStrokeRadius)
 
 DEF_TEST (CullTestTest, reporter)
 
static bool is_linear (SkPoint p0, SkPoint p1, SkPoint p2)
 
static bool is_linear (const SkPoint p[4])
 
static void check_cubic_convex_180 (skiatest::Reporter *r, const SkPoint p[4])
 
 DEF_TEST (FindCubicConvex180Chops, r)
 
 DEF_TEST (PreChopPathCurves, reporter)
 
static float wangs_formula_quadratic_reference_impl (float precision, const SkPoint p[3])
 
static float wangs_formula_cubic_reference_impl (float precision, const SkPoint p[4])
 
static float wangs_formula_conic_reference_impl (float precision, const SkPoint P[3], const float w)
 
static void for_random_matrices (SkRandom *rand, const std::function< void(const SkMatrix &)> &f)
 
static void for_random_beziers (int numPoints, SkRandom *rand, const std::function< void(const SkPoint[])> &f, int maxExponent=30)
 
 DEF_TEST (wangs_formula_log2, r)
 
 DEF_TEST (wangs_formula_vectorXforms, r)
 
 DEF_TEST (wangs_formula_worst_case_cubic, r)
 
 DEF_TEST (wangs_formula_quad_within_tol, r)
 
 DEF_TEST (wangs_formula_rational_quad_reduces, r)
 
 DEF_TEST (wangs_formula_conic_within_tol, r)
 
 DEF_TEST (wangs_formula_conic_matches_reference, r)
 
 DEF_TEST (wangs_formula_conic_vectorXforms, r)
 
 DEF_TEST (wangs_formula_nextlog2, r)
 

Variables

static constexpr float kPrecision = 4
 
static constexpr int kMaxResolveLevel = 5
 
static constexpr int kMaxParametricSegments = 1 << kMaxResolveLevel
 
static constexpr int kMaxParametricSegments_p2 = kMaxParametricSegments * kMaxParametricSegments
 
static constexpr int kMaxParametricSegments_p4
 
static constexpr float kMaxSegmentsPerCurve = 1024
 
static constexpr float kMaxSegmentsPerCurve_p2 = kMaxSegmentsPerCurve * kMaxSegmentsPerCurve
 
static constexpr float kMaxSegmentsPerCurve_p4 = kMaxSegmentsPerCurve_p2 * kMaxSegmentsPerCurve_p2
 
static constexpr float kCubicCurveType = 0
 
static constexpr float kConicCurveType = 1
 
static constexpr float kTriangularConicCurveType = 2
 
const SkMatrix gMatrices []
 
const SkPoint kSerp [4]
 
const SkPoint kLoop [4]
 
const SkPoint kQuad [4]
 

Enumeration Type Documentation

◆ PatchAttribs

enum class skgpu::tess::PatchAttribs
strong
Enumerator
kNone 
kJoinControlPoint 
kFanPoint 
kStrokeParams 
kColor 
kPaintDepth 
kExplicitCurveType 
kSsboIndex 
kWideColorIfEnabled 

Definition at line 77 of file Tessellation.h.

77 {
78 // Attribs.
79 kNone = 0,
80 kJoinControlPoint = 1 << 0, // [float2] Used by strokes. This defines tangent direction.
81 kFanPoint = 1 << 1, // [float2] Used by wedges. This is the center point the wedges fan around.
82 kStrokeParams = 1 << 2, // [float2] Used when strokes have different widths or join types.
83 kColor = 1 << 3, // [ubyte4 or float4] Used when patches have different colors.
84 kPaintDepth = 1 << 4, // [float] Used in Graphite to specify depth attachment value for draw.
85 kExplicitCurveType = 1 << 5, // [float] Used when GPU can't infer curve type based on infinity.
86 kSsboIndex = 1 << 7, // [int] Used to index into a shared storage buffer for this patch's
87 // uniform values.
88
89 // Extra flags.
90 kWideColorIfEnabled = 1 << 6, // If kColor is set, specifies it to be float4 wide color.
91};
static constexpr SkColor kColor

Function Documentation

◆ CalcNumRadialSegmentsPerRadian()

float skgpu::tess::CalcNumRadialSegmentsPerRadian ( float  approxDevStrokeRadius)
inline

Definition at line 210 of file Tessellation.h.

210 {
211 float cosTheta = 1.f - (1.f / kPrecision) / approxDevStrokeRadius;
212 return .5f / acosf(std::max(cosTheta, -1.f));
213}
static constexpr float kPrecision

◆ check_cubic_convex_180()

static void skgpu::tess::check_cubic_convex_180 ( skiatest::Reporter r,
const SkPoint  p[4] 
)
static

Definition at line 29 of file FindCubicConvex180ChopsTest.cpp.

29 {
30 bool areCusps = false;
31 float inflectT[2], convex180T[2];
32 if (int inflectN = SkFindCubicInflections(p, inflectT)) {
33 // The curve has inflections. FindCubicConvex180Chops should return the inflection
34 // points.
35 int convex180N = FindCubicConvex180Chops(p, convex180T, &areCusps);
36 REPORTER_ASSERT(r, inflectN == convex180N);
37 if (!areCusps) {
38 REPORTER_ASSERT(r, inflectN == 1 ||
39 fabsf(inflectT[0] - inflectT[1]) >= SK_ScalarNearlyZero);
40 }
41 for (int i = 0; i < convex180N; ++i) {
42 REPORTER_ASSERT(r, SkScalarNearlyEqual(inflectT[i], convex180T[i]));
43 }
44 } else {
45 float totalRotation = SkMeasureNonInflectCubicRotation(p);
46 int convex180N = FindCubicConvex180Chops(p, convex180T, &areCusps);
47 SkPoint chops[10];
48 SkChopCubicAt(p, chops, convex180T, convex180N);
49 float radsSum = 0;
50 for (int i = 0; i <= convex180N; ++i) {
51 float rads = SkMeasureNonInflectCubicRotation(chops + i*3);
53 radsSum += rads;
54 }
55 if (totalRotation < SK_ScalarPI - SK_ScalarNearlyZero) {
56 // The curve should never chop if rotation is <180 degrees.
57 REPORTER_ASSERT(r, convex180N == 0);
58 } else if (!is_linear(p)) {
59 REPORTER_ASSERT(r, SkScalarNearlyEqual(radsSum, totalRotation));
60 if (totalRotation > SK_ScalarPI + SK_ScalarNearlyZero) {
61 REPORTER_ASSERT(r, convex180N == 1);
62 // This works because cusps take the "inflection" path above, so we don't get
63 // non-lilnear curves that lose rotation when chopped.
67 SkMeasureNonInflectCubicRotation(chops + 3), totalRotation - SK_ScalarPI));
68 }
69 REPORTER_ASSERT(r, !areCusps);
70 } else {
71 REPORTER_ASSERT(r, areCusps);
72 }
73 }
74}
#define SkASSERT(cond)
Definition SkAssert.h:116
float SkMeasureNonInflectCubicRotation(const SkPoint pts[4])
void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t)
int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2])
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SK_ScalarNearlyZero
Definition SkScalar.h:99
#define SK_ScalarPI
Definition SkScalar.h:21
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
static bool is_linear(SkPoint p0, SkPoint p1, SkPoint p2)
int FindCubicConvex180Chops(const SkPoint pts[], float T[2], bool *areCusps)

◆ ConicHasCusp()

bool skgpu::tess::ConicHasCusp ( const SkPoint  p[3])
inline

Definition at line 131 of file Tessellation.h.

131 {
132 SkVector a = p[1] - p[0];
133 SkVector b = p[2] - p[1];
134 // A conic of any class can only have a cusp if it is a degenerate flat line with a 180 degree
135 // turnarund. To detect this, the beginning and ending tangents must be parallel
136 // (a.cross(b) == 0) and pointing in opposite directions (a.dot(b) < 0).
137 return a.cross(b) == 0 && a.dot(b) < 0;
138}
static bool b
struct MyStruct a[10]

◆ DEF_TEST() [1/12]

skgpu::tess::DEF_TEST ( CullTestTest  ,
reporter   
)

Definition at line 35 of file CullTestTest.cpp.

35 {
36 SkRandom rand;
37 float l=10, t=2000, r=100, b=2064;
38 SkRect viewportRect{l, t, r, b};
39 float valuesL[4] = {l-20, l-10, l+10, l+20};
40 float valuesT[4] = {t-20, t-10, t+10, t+20};
41 float valuesR[4] = {r+20, r+10, r-10, r-20};
42 float valuesB[4] = {b+20, b+10, b-10, b-20};
43 for (SkMatrix m : gMatrices) {
44 CullTest cullTest(viewportRect, m);
45 SkMatrix inverse;
46 SkAssertResult(m.invert(&inverse));
47 for (const float* y : {valuesT, valuesB}) {
48 for (const float* x : {valuesL, valuesR}) {
49 for (int i = 0; i < 500; ++i) {
50 int mask = rand.nextU();
51 const SkPoint devPts[4] = {{x[(mask >> 0) & 3], y[(mask >> 2) & 3]},
52 {x[(mask >> 4) & 3], y[(mask >> 6) & 3]},
53 {x[(mask >> 8) & 3], y[(mask >> 10) & 3]},
54 {x[(mask >> 12) & 3], y[(mask >> 14) & 3]}};
55
56 SkPoint localPts[4];
57 inverse.mapPoints(localPts, devPts, 4);
58
60 cullTest.isVisible(localPts[0]) ==
61 viewportRect.contains(devPts[0].fX, devPts[0].fY));
62
63 {
64 SkRect devBounds3;
65 devBounds3.setBounds(devPts, 3);
66 // Outset devBounds because SkRect::intersects returns false on empty, which is NOT
67 // the behavior we want.
68 devBounds3.outset(1e-3f, 1e-3f);
70 cullTest.areVisible3(localPts) == viewportRect.intersects(devBounds3));
71 }
72
73 {
74 SkRect devBounds4;
75 devBounds4.setBounds(devPts, 4);
76 // Outset devBounds because SkRect::intersects returns false on empty, which is NOT
77 // the behavior we want.
78 devBounds4.outset(1e-3f, 1e-3f);
80 cullTest.areVisible4(localPts) == viewportRect.intersects(devBounds4));
81 }
82 }}}
83 }
84}
reporter
#define SkAssertResult(cond)
Definition SkAssert.h:123
void mapPoints(SkPoint dst[], const SkPoint src[], int count) const
Definition SkMatrix.cpp:770
uint32_t nextU()
Definition SkRandom.h:42
double y
double x
const SkMatrix gMatrices[]
void outset(float dx, float dy)
Definition SkRect.h:1077
void setBounds(const SkPoint pts[], int count)
Definition SkRect.h:881

◆ DEF_TEST() [2/12]

skgpu::tess::DEF_TEST ( FindCubicConvex180Chops  ,
 
)

Definition at line 76 of file FindCubicConvex180ChopsTest.cpp.

76 {
77 // Test all combinations of corners from the square [0,0,1,1]. This covers every cubic type as
78 // well as a wide variety of special cases for cusps, lines, loops, and inflections.
79 for (int i = 0; i < (1 << 8); ++i) {
80 SkPoint p[4] = {SkPoint::Make((i>>0)&1, (i>>1)&1),
81 SkPoint::Make((i>>2)&1, (i>>3)&1),
82 SkPoint::Make((i>>4)&1, (i>>5)&1),
83 SkPoint::Make((i>>6)&1, (i>>7)&1)};
84 check_cubic_convex_180(r, p);
85 }
86
87 {
88 // This cubic has a convex-180 chop at T=1-"epsilon"
89 static const uint32_t hexPts[] = {0x3ee0ac74, 0x3f1e061a, 0x3e0fc408, 0x3f457230,
90 0x3f42ac7c, 0x3f70d76c, 0x3f4e6520, 0x3f6acafa};
91 SkPoint p[4];
92 memcpy(p, hexPts, sizeof(p));
94 }
95
96 // Now test an exact quadratic.
97 SkPoint quad[4] = {{0,0}, {2,2}, {4,2}, {6,0}};
98 float T[2];
99 bool areCusps;
100 REPORTER_ASSERT(r, FindCubicConvex180Chops(quad, T, &areCusps) == 0);
101
102 // Now test that cusps and near-cusps get flagged as cusps.
103 SkPoint cusp[4] = {{0,0}, {1,1}, {1,0}, {0,1}};
104 REPORTER_ASSERT(r, FindCubicConvex180Chops(cusp, T, &areCusps) == 1);
105 REPORTER_ASSERT(r, areCusps == true);
106
107 // Find the height of the right side of "cusp" at which the distance between its inflection
108 // points is kEpsilon (in parametric space).
109 constexpr static double kEpsilon = 1.0 / (1 << 11);
110 constexpr static double kEpsilonSquared = kEpsilon * kEpsilon;
111 double h = (1 - kEpsilonSquared) / (3 * kEpsilonSquared + 1);
112 double dy = (1 - h) / 2;
113 cusp[1].fY = (float)(1 - dy);
114 cusp[2].fY = (float)(0 + dy);
116 REPORTER_ASSERT(r, SkScalarNearlyEqual(T[1] - T[0], (float)kEpsilon, (float)kEpsilonSquared));
117
118 // Ensure two inflection points barely more than kEpsilon apart do not get flagged as cusps.
119 cusp[1].fY = (float)(1 - 1.1 * dy);
120 cusp[2].fY = (float)(0 + 1.1 * dy);
121 REPORTER_ASSERT(r, FindCubicConvex180Chops(cusp, T, &areCusps) == 2);
122 REPORTER_ASSERT(r, areCusps == false);
123
124 // Ensure two inflection points barely less than kEpsilon apart do get flagged as cusps.
125 cusp[1].fY = (float)(1 - .9 * dy);
126 cusp[2].fY = (float)(0 + .9 * dy);
127 REPORTER_ASSERT(r, FindCubicConvex180Chops(cusp, T, &areCusps) == 1);
128 REPORTER_ASSERT(r, areCusps == true);
129}
static constexpr double kEpsilon
static void check_cubic_convex_180(skiatest::Reporter *r, const SkPoint p[4])
SkScalar h
#define T
static constexpr SkPoint Make(float x, float y)
float fY
y-axis value

◆ DEF_TEST() [3/12]

skgpu::tess::DEF_TEST ( PreChopPathCurves  ,
reporter   
)

Definition at line 15 of file PreChopPathCurvesTest.cpp.

15 {
16 // These particular test cases can get stuck in infinite recursion due to limited fp32
17 // precision. (Although they will not with the provided tessellationPrecision values; we had to
18 // lower precision in order to avoid the "viewport size" assert in PreChopPathCurves.) Bump the
19 // tessellationPreciion up to 4 and run these tests in order to verify our bail condition for
20 // infinite recursion caused by bad fp32 precision. If the test completes, it passed.
21 SkPath p = SkPath().moveTo(11.171727877046647f, -11.78621173228717f)
22 .quadTo(11.171727877046647f, -11.78621173228717f,
23 8.33583747124031f, 77.27177002747368f)
24 .cubicTo(8.33583747124031f, 77.27177002747368f,
25 8.33583747124031f, 77.27177002747368f,
26 11.171727877046647f, -11.78621173228717f)
27 .conicTo(11.171727877046647f, -11.78621173228717f,
28 8.33583747124031f, 77.27177002747368f,
29 1e-6f)
30 .conicTo(8.33583747124031f, 77.27177002747368f,
31 11.171727877046647f, -11.78621173228717f,
32 1e6f);
33
34 SkMatrix m = SkMatrix::Scale(138.68622826903837f, 74192976757580.44189f);
35 PreChopPathCurves(1/16.f, p, m, {1000, -74088852800000.f, 3000, -74088852700000.f});
36
37 m = SkMatrix::Scale(138.68622826903837f, 74192976757580.44189f*.3f);
38 PreChopPathCurves(.25f, p, m, {1000, -22226658140000.f, 3000, -22226658130000.f});
39
40 m = SkMatrix::Scale(138.68622826903837f, 74192976757580.44189f/4);
41 PreChopPathCurves(.25f, p, m, {1000, -18522213200000.f, 3000, -18522213100000.f});
42}
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
SkPath & moveTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:678
SkPath & quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
Definition SkPath.cpp:736
SkPath & cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3)
Definition SkPath.cpp:789
SkPath & conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w)
Definition SkPath.cpp:756
SkPath PreChopPathCurves(float tessellationPrecision, const SkPath &path, const SkMatrix &matrix, const SkRect &viewport)

◆ DEF_TEST() [4/12]

skgpu::tess::DEF_TEST ( wangs_formula_conic_matches_reference  ,
 
)

Definition at line 492 of file WangsFormulaTest.cpp.

492 {
493 SkRandom rand;
494 for (int i = -10; i <= 10; ++i) {
495 const float w = std::ldexp(1 + rand.nextF(), i);
496 for_random_beziers(3, &rand, [&r, w](const SkPoint pts[]) {
497 const float ref_nsegs = wangs_formula_conic_reference_impl(kPrecision, pts, w);
498 const float nsegs = wangs_formula::conic(kPrecision, pts, w);
499
500 // Because the Gr version may implement the math differently for performance,
501 // allow different slack in the comparison based on the rough scale of the answer.
502 const float cmpThresh = ref_nsegs * (1.f / (1 << 20));
503 REPORTER_ASSERT(r, SkScalarNearlyEqual(ref_nsegs, nsegs, cmpThresh));
504 });
505 }
506}
float nextF()
Definition SkRandom.h:55
static void for_random_beziers(int numPoints, SkRandom *rand, const std::function< void(const SkPoint[])> &f, int maxExponent=30)
static float wangs_formula_conic_reference_impl(float precision, const SkPoint P[3], const float w)
SkScalar w

◆ DEF_TEST() [5/12]

skgpu::tess::DEF_TEST ( wangs_formula_conic_vectorXforms  ,
 
)

Definition at line 509 of file WangsFormulaTest.cpp.

509 {
510 auto check_conic_with_transform = [&](const SkPoint* pts, float w, const SkMatrix& m) {
511 SkPoint ptsXformed[3];
512 m.mapPoints(ptsXformed, pts, 3);
513 float expected = wangs_formula::conic(kPrecision, ptsXformed, w);
514 float actual = wangs_formula::conic(kPrecision, pts, w, wangs_formula::VectorXform(m));
515 REPORTER_ASSERT(r, SkScalarNearlyEqual(actual, expected));
516 };
517
518 SkRandom rand;
519 for (int i = -10; i <= 10; ++i) {
520 const float w = std::ldexp(1 + rand.nextF(), i);
521 for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
522 check_conic_with_transform(pts, w, SkMatrix::I());
523 check_conic_with_transform(
524 pts, w, SkMatrix::Scale(rand.nextRangeF(-10, 10), rand.nextRangeF(-10, 10)));
525
526 // Random 2x2 matrix
527 SkMatrix m;
528 m.setScaleX(rand.nextRangeF(-10, 10));
529 m.setSkewX(rand.nextRangeF(-10, 10));
530 m.setSkewY(rand.nextRangeF(-10, 10));
531 m.setScaleY(rand.nextRangeF(-10, 10));
532 check_conic_with_transform(pts, w, m);
533 });
534 }
535}
static const SkMatrix & I()
float nextRangeF(float min, float max)
Definition SkRandom.h:64

◆ DEF_TEST() [6/12]

skgpu::tess::DEF_TEST ( wangs_formula_conic_within_tol  ,
 
)

Definition at line 430 of file WangsFormulaTest.cpp.

430 {
431 constexpr int maxExponent = 24;
432
433 // Single-precision functions in SkConic/SkGeometry lose too much accuracy with
434 // large-magnitude curves and large weights for this test to pass.
435 using Sk2d = skvx::Vec<2, double>;
436 const auto eval_conic = [](const SkPoint pts[3], float w, float t) -> Sk2d {
437 const auto eval = [](Sk2d A, Sk2d B, Sk2d C, float t) -> Sk2d {
438 return (A * t + B) * t + C;
439 };
440
441 const Sk2d p0 = {pts[0].fX, pts[0].fY};
442 const Sk2d p1 = {pts[1].fX, pts[1].fY};
443 const Sk2d p1w = p1 * w;
444 const Sk2d p2 = {pts[2].fX, pts[2].fY};
445 Sk2d numer = eval(p2 - p1w * 2 + p0, (p1w - p0) * 2, p0, t);
446
447 Sk2d denomC = {1, 1};
448 Sk2d denomB = {2 * (w - 1), 2 * (w - 1)};
449 Sk2d denomA = {-2 * (w - 1), -2 * (w - 1)};
450 Sk2d denom = eval(denomA, denomB, denomC, t);
451 return numer / denom;
452 };
453
454 const auto dot = [](const Sk2d& a, const Sk2d& b) -> double {
455 return a[0] * b[0] + a[1] * b[1];
456 };
457
458 const auto length = [](const Sk2d& p) -> double { return sqrt(p[0] * p[0] + p[1] * p[1]); };
459
460 SkRandom rand;
461 for (int i = -10; i <= 10; ++i) {
462 const float w = std::ldexp(1 + rand.nextF(), i);
464 3, &rand,
465 [&](const SkPoint pts[]) {
466 const int nsegs = SkScalarCeilToInt(wangs_formula::conic(kPrecision, pts, w));
467
468 const float tdelta = 1.f / nsegs;
469 for (int j = 0; j < nsegs; ++j) {
470 const float tmin = j * tdelta, tmax = (j + 1) * tdelta,
471 tmid = 0.5f * (tmin + tmax);
472
473 Sk2d p0, p1, p2;
474 p0 = eval_conic(pts, w, tmin);
475 p1 = eval_conic(pts, w, tmid);
476 p2 = eval_conic(pts, w, tmax);
477
478 // Get distance of p1 to baseline (p0, p2).
479 const Sk2d n = {p2[1] - p0[1], p0[0] - p2[0]};
480 SkASSERT(length(n) != 0);
481 const double d = std::abs(dot(p1 - p0, n)) / length(n);
482
483 // Check distance is within tolerance
484 REPORTER_ASSERT(r, d <= (1.0 / kPrecision) + SK_ScalarNearlyZero);
485 }
486 },
487 maxExponent);
488 }
489}
#define SkScalarCeilToInt(x)
Definition SkScalar.h:36
#define C(TEST_CATEGORY)
Definition colrv1.cpp:247
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
#define B
size_t length
SINT T dot(const Vec< N, T > &a, const Vec< N, T > &b)
Definition SkVx.h:964
SIN Vec< N, float > sqrt(const Vec< N, float > &x)
Definition SkVx.h:706
float fX
x-axis value

◆ DEF_TEST() [7/12]

skgpu::tess::DEF_TEST ( wangs_formula_log2  ,
 
)

Definition at line 129 of file WangsFormulaTest.cpp.

129 {
130 // Constructs a cubic such that the 'length' term in wang's formula == term.
131 //
132 // f = sqrt(k * length(max(abs(p0 - p1*2 + p2),
133 // abs(p1 - p2*2 + p3))));
134 auto setupCubicLengthTerm = [](int seed, SkPoint pts[], float term) {
135 memset(pts, 0, sizeof(SkPoint) * 4);
136
137 SkPoint term2d = (seed & 1) ?
138 SkPoint::Make(term, 0) : SkPoint::Make(.5f, std::sqrt(3)/2) * term;
139 seed >>= 1;
140
141 if (seed & 1) {
142 term2d.fX = -term2d.fX;
143 }
144 seed >>= 1;
145
146 if (seed & 1) {
147 std::swap(term2d.fX, term2d.fY);
148 }
149 seed >>= 1;
150
151 switch (seed % 4) {
152 case 0:
153 pts[0] = term2d;
154 pts[3] = term2d * .75f;
155 return;
156 case 1:
157 pts[1] = term2d * -.5f;
158 return;
159 case 2:
160 pts[1] = term2d * -.5f;
161 return;
162 case 3:
163 pts[3] = term2d;
164 pts[0] = term2d * .75f;
165 return;
166 }
167 };
168
169 // Constructs a quadratic such that the 'length' term in wang's formula == term.
170 //
171 // f = sqrt(k * length(p0 - p1*2 + p2));
172 auto setupQuadraticLengthTerm = [](int seed, SkPoint pts[], float term) {
173 memset(pts, 0, sizeof(SkPoint) * 3);
174
175 SkPoint term2d = (seed & 1) ?
176 SkPoint::Make(term, 0) : SkPoint::Make(.5f, std::sqrt(3)/2) * term;
177 seed >>= 1;
178
179 if (seed & 1) {
180 term2d.fX = -term2d.fX;
181 }
182 seed >>= 1;
183
184 if (seed & 1) {
185 std::swap(term2d.fX, term2d.fY);
186 }
187 seed >>= 1;
188
189 switch (seed % 3) {
190 case 0:
191 pts[0] = term2d;
192 return;
193 case 1:
194 pts[1] = term2d * -.5f;
195 return;
196 case 2:
197 pts[2] = term2d;
198 return;
199 }
200 };
201
202 // wangs_formula_cubic and wangs_formula_quadratic both use rsqrt instead of sqrt for speed.
203 // Linearization is all approximate anyway, so as long as we are within ~1/2 tessellation
204 // segment of the reference value we are good enough.
205 constexpr static float kTessellationTolerance = 1/128.f;
206
207 for (int level = 0; level < 30; ++level) {
208 float epsilon = std::ldexp(SK_ScalarNearlyZero, level * 2);
209 SkPoint pts[4];
210
211 {
212 // Test cubic boundaries.
213 // f = sqrt(k * length(max(abs(p0 - p1*2 + p2),
214 // abs(p1 - p2*2 + p3))));
215 constexpr static float k = (3 * 2) / (8 * (1.f/kPrecision));
216 float x = std::ldexp(1, level * 2) / k;
217 setupCubicLengthTerm(level << 1, pts, x - epsilon);
218 float referenceValue = wangs_formula_cubic_reference_impl(kPrecision, pts);
219 REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level);
220 float c = wangs_formula::cubic(kPrecision, pts);
221 REPORTER_ASSERT(r, SkScalarNearlyEqual(c/referenceValue, 1, kTessellationTolerance));
222 REPORTER_ASSERT(r, wangs_formula::cubic_log2(kPrecision, pts) == level);
223 setupCubicLengthTerm(level << 1, pts, x + epsilon);
224 referenceValue = wangs_formula_cubic_reference_impl(kPrecision, pts);
225 REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level + 1);
226 c = wangs_formula::cubic(kPrecision, pts);
227 REPORTER_ASSERT(r, SkScalarNearlyEqual(c/referenceValue, 1, kTessellationTolerance));
228 REPORTER_ASSERT(r, wangs_formula::cubic_log2(kPrecision, pts) == level + 1);
229 }
230
231 {
232 // Test quadratic boundaries.
233 // f = std::sqrt(k * Length(p0 - p1*2 + p2));
234 constexpr static float k = 2 / (8 * (1.f/kPrecision));
235 float x = std::ldexp(1, level * 2) / k;
236 setupQuadraticLengthTerm(level << 1, pts, x - epsilon);
237 float referenceValue = wangs_formula_quadratic_reference_impl(kPrecision, pts);
238 REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level);
239 float q = wangs_formula::quadratic(kPrecision, pts);
240 REPORTER_ASSERT(r, SkScalarNearlyEqual(q/referenceValue, 1, kTessellationTolerance));
241 REPORTER_ASSERT(r, wangs_formula::quadratic_log2(kPrecision, pts) == level);
242 setupQuadraticLengthTerm(level << 1, pts, x + epsilon);
243 referenceValue = wangs_formula_quadratic_reference_impl(kPrecision, pts);
244 REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level+1);
245 q = wangs_formula::quadratic(kPrecision, pts);
246 REPORTER_ASSERT(r, SkScalarNearlyEqual(q/referenceValue, 1, kTessellationTolerance));
247 REPORTER_ASSERT(r, wangs_formula::quadratic_log2(kPrecision, pts) == level + 1);
248 }
249 }
250
251 auto check_cubic_log2 = [&](const SkPoint* pts) {
252 float f = std::max(1.f, wangs_formula_cubic_reference_impl(kPrecision, pts));
253 int f_log2 = wangs_formula::cubic_log2(kPrecision, pts);
254 REPORTER_ASSERT(r, SkScalarCeilToInt(std::log2(f)) == f_log2);
255 float c = std::max(1.f, wangs_formula::cubic(kPrecision, pts));
256 REPORTER_ASSERT(r, SkScalarNearlyEqual(c/f, 1, kTessellationTolerance));
257 };
258
259 auto check_quadratic_log2 = [&](const SkPoint* pts) {
260 float f = std::max(1.f, wangs_formula_quadratic_reference_impl(kPrecision, pts));
261 int f_log2 = wangs_formula::quadratic_log2(kPrecision, pts);
262 REPORTER_ASSERT(r, SkScalarCeilToInt(std::log2(f)) == f_log2);
263 float q = std::max(1.f, wangs_formula::quadratic(kPrecision, pts));
264 REPORTER_ASSERT(r, SkScalarNearlyEqual(q/f, 1, kTessellationTolerance));
265 };
266
267 SkRandom rand;
268
269 for_random_matrices(&rand, [&](const SkMatrix& m) {
270 SkPoint pts[4];
271 m.mapPoints(pts, kSerp, 4);
272 check_cubic_log2(pts);
273
274 m.mapPoints(pts, kLoop, 4);
275 check_cubic_log2(pts);
276
277 m.mapPoints(pts, kQuad, 3);
278 check_quadratic_log2(pts);
279 });
280
281 for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
282 check_cubic_log2(pts);
283 });
284
285 for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
286 check_quadratic_log2(pts);
287 });
288}
static void for_random_matrices(SkRandom *rand, const std::function< void(const SkMatrix &)> &f)
static float wangs_formula_quadratic_reference_impl(float precision, const SkPoint p[3])
static float wangs_formula_cubic_reference_impl(float precision, const SkPoint p[4])

◆ DEF_TEST() [8/12]

skgpu::tess::DEF_TEST ( wangs_formula_nextlog2  ,
 
)

Definition at line 537 of file WangsFormulaTest.cpp.

537 {
538 REPORTER_ASSERT(r, 0b0'00000000'111'1111111111'1111111111 == (1u << 23) - 1u);
539 REPORTER_ASSERT(r, wangs_formula::nextlog2(-std::numeric_limits<float>::infinity()) == 0);
540 REPORTER_ASSERT(r, wangs_formula::nextlog2(-std::numeric_limits<float>::max()) == 0);
541 REPORTER_ASSERT(r, wangs_formula::nextlog2(-1000.0f) == 0);
542 REPORTER_ASSERT(r, wangs_formula::nextlog2(-0.1f) == 0);
543 REPORTER_ASSERT(r, wangs_formula::nextlog2(-std::numeric_limits<float>::min()) == 0);
544 REPORTER_ASSERT(r, wangs_formula::nextlog2(-std::numeric_limits<float>::denorm_min()) == 0);
545 REPORTER_ASSERT(r, wangs_formula::nextlog2(0.0f) == 0);
546 REPORTER_ASSERT(r, wangs_formula::nextlog2(std::numeric_limits<float>::denorm_min()) == 0);
547 REPORTER_ASSERT(r, wangs_formula::nextlog2(std::numeric_limits<float>::min()) == 0);
548 REPORTER_ASSERT(r, wangs_formula::nextlog2(0.1f) == 0);
549 REPORTER_ASSERT(r, wangs_formula::nextlog2(1.0f) == 0);
550 REPORTER_ASSERT(r, wangs_formula::nextlog2(1.1f) == 1);
551 REPORTER_ASSERT(r, wangs_formula::nextlog2(2.0f) == 1);
552 REPORTER_ASSERT(r, wangs_formula::nextlog2(2.1f) == 2);
553 REPORTER_ASSERT(r, wangs_formula::nextlog2(3.0f) == 2);
554 REPORTER_ASSERT(r, wangs_formula::nextlog2(3.1f) == 2);
555 REPORTER_ASSERT(r, wangs_formula::nextlog2(4.0f) == 2);
556 REPORTER_ASSERT(r, wangs_formula::nextlog2(4.1f) == 3);
557 REPORTER_ASSERT(r, wangs_formula::nextlog2(5.0f) == 3);
558 REPORTER_ASSERT(r, wangs_formula::nextlog2(5.1f) == 3);
559 REPORTER_ASSERT(r, wangs_formula::nextlog2(6.0f) == 3);
560 REPORTER_ASSERT(r, wangs_formula::nextlog2(6.1f) == 3);
561 REPORTER_ASSERT(r, wangs_formula::nextlog2(7.0f) == 3);
562 REPORTER_ASSERT(r, wangs_formula::nextlog2(7.1f) == 3);
563 REPORTER_ASSERT(r, wangs_formula::nextlog2(8.0f) == 3);
564 REPORTER_ASSERT(r, wangs_formula::nextlog2(8.1f) == 4);
565 REPORTER_ASSERT(r, wangs_formula::nextlog2(9.0f) == 4);
566 REPORTER_ASSERT(r, wangs_formula::nextlog2(9.1f) == 4);
567 REPORTER_ASSERT(r, wangs_formula::nextlog2(std::numeric_limits<float>::max()) == 128);
568 REPORTER_ASSERT(r, wangs_formula::nextlog2(std::numeric_limits<float>::infinity()) == 128);
569 REPORTER_ASSERT(r, wangs_formula::nextlog2(std::numeric_limits<float>::quiet_NaN()) == 0);
570 REPORTER_ASSERT(r, wangs_formula::nextlog2(-std::numeric_limits<float>::quiet_NaN()) == 0);
571
572 for (int i = 0; i < 100; ++i) {
573 float pow2 = std::ldexp(1, i);
574 float epsilon = std::ldexp(SK_ScalarNearlyZero, i);
575 REPORTER_ASSERT(r, wangs_formula::nextlog2(pow2) == i);
576 REPORTER_ASSERT(r, wangs_formula::nextlog2(pow2 + epsilon) == i + 1);
577 REPORTER_ASSERT(r, wangs_formula::nextlog2(pow2 - epsilon) == i);
578 }
579}

◆ DEF_TEST() [9/12]

skgpu::tess::DEF_TEST ( wangs_formula_quad_within_tol  ,
 
)

Definition at line 363 of file WangsFormulaTest.cpp.

363 {
364 // Wang's formula and the quad math starts to lose precision with very large
365 // coordinate values, so limit the magnitude a bit to prevent test failures
366 // due to loss of precision.
367 constexpr int maxExponent = 15;
368 SkRandom rand;
369 for_random_beziers(3, &rand, [&r](const SkPoint pts[]) {
370 const int nsegs = static_cast<int>(
371 std::ceil(wangs_formula_quadratic_reference_impl(kPrecision, pts)));
372
373 const float tdelta = 1.f / nsegs;
374 for (int j = 0; j < nsegs; ++j) {
375 const float tmin = j * tdelta, tmax = (j + 1) * tdelta;
376
377 // Get section of quad in [tmin,tmax]
378 const SkPoint* sectionPts;
379 SkPoint tmp0[5];
380 SkPoint tmp1[5];
381 if (tmin == 0) {
382 if (tmax == 1) {
383 sectionPts = pts;
384 } else {
385 SkChopQuadAt(pts, tmp0, tmax);
386 sectionPts = tmp0;
387 }
388 } else {
389 SkChopQuadAt(pts, tmp0, tmin);
390 if (tmax == 1) {
391 sectionPts = tmp0 + 2;
392 } else {
393 SkChopQuadAt(tmp0 + 2, tmp1, (tmax - tmin) / (1 - tmin));
394 sectionPts = tmp1;
395 }
396 }
397
398 // For quads, max distance from baseline is always at t=0.5.
399 SkPoint p;
400 p = SkEvalQuadAt(sectionPts, 0.5f);
401
402 // Get distance of p to baseline
403 const SkPoint n = {sectionPts[2].fY - sectionPts[0].fY,
404 sectionPts[0].fX - sectionPts[2].fX};
405 const float d = std::abs((p - sectionPts[0]).dot(n)) / n.length();
406
407 // Check distance is within specified tolerance
408 REPORTER_ASSERT(r, d <= (1.f / kPrecision) + SK_ScalarNearlyZero);
409 }
410 }, maxExponent);
411}
void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t)
void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint *pt, SkVector *tangent)
float length() const

◆ DEF_TEST() [10/12]

skgpu::tess::DEF_TEST ( wangs_formula_rational_quad_reduces  ,
 
)

Definition at line 415 of file WangsFormulaTest.cpp.

415 {
416 constexpr static float kTessellationTolerance = 1 / 128.f;
417
418 SkRandom rand;
419 for (int i = 0; i < 100; ++i) {
420 for_random_beziers(3, &rand, [&r](const SkPoint pts[]) {
421 const float rational_nsegs = wangs_formula::conic(kPrecision, pts, 1.f);
422 const float integral_nsegs = wangs_formula_quadratic_reference_impl(kPrecision, pts);
424 r, SkScalarNearlyEqual(rational_nsegs, integral_nsegs, kTessellationTolerance));
425 });
426 }
427}

◆ DEF_TEST() [11/12]

skgpu::tess::DEF_TEST ( wangs_formula_vectorXforms  ,
 
)

Definition at line 291 of file WangsFormulaTest.cpp.

291 {
292 auto check_cubic_log2_with_transform = [&](const SkPoint* pts, const SkMatrix& m){
293 SkPoint ptsXformed[4];
294 m.mapPoints(ptsXformed, pts, 4);
295 int expected = wangs_formula::cubic_log2(kPrecision, ptsXformed);
296 int actual = wangs_formula::cubic_log2(kPrecision, pts, wangs_formula::VectorXform(m));
297 REPORTER_ASSERT(r, actual == expected);
298 };
299
300 auto check_quadratic_log2_with_transform = [&](const SkPoint* pts, const SkMatrix& m) {
301 SkPoint ptsXformed[3];
302 m.mapPoints(ptsXformed, pts, 3);
303 int expected = wangs_formula::quadratic_log2(kPrecision, ptsXformed);
304 int actual = wangs_formula::quadratic_log2(kPrecision, pts, wangs_formula::VectorXform(m));
305 REPORTER_ASSERT(r, actual == expected);
306 };
307
308 SkRandom rand;
309
310 for_random_matrices(&rand, [&](const SkMatrix& m) {
311 check_cubic_log2_with_transform(kSerp, m);
312 check_cubic_log2_with_transform(kLoop, m);
313 check_quadratic_log2_with_transform(kQuad, m);
314
315 for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
316 check_cubic_log2_with_transform(pts, m);
317 });
318
319 for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
320 check_quadratic_log2_with_transform(pts, m);
321 });
322 });
323}

◆ DEF_TEST() [12/12]

skgpu::tess::DEF_TEST ( wangs_formula_worst_case_cubic  ,
 
)

Definition at line 325 of file WangsFormulaTest.cpp.

325 {
326 {
327 SkPoint worstP[] = {{0,0}, {100,100}, {0,0}, {0,0}};
328 REPORTER_ASSERT(r, wangs_formula::worst_case_cubic(kPrecision, 100, 100) ==
329 wangs_formula_cubic_reference_impl(kPrecision, worstP));
330 REPORTER_ASSERT(r, wangs_formula::worst_case_cubic_log2(kPrecision, 100, 100) ==
331 wangs_formula::cubic_log2(kPrecision, worstP));
332 }
333 {
334 SkPoint worstP[] = {{100,100}, {100,100}, {200,200}, {100,100}};
335 REPORTER_ASSERT(r, wangs_formula::worst_case_cubic(kPrecision, 100, 100) ==
336 wangs_formula_cubic_reference_impl(kPrecision, worstP));
337 REPORTER_ASSERT(r, wangs_formula::worst_case_cubic_log2(kPrecision, 100, 100) ==
338 wangs_formula::cubic_log2(kPrecision, worstP));
339 }
340 auto check_worst_case_cubic = [&](const SkPoint* pts) {
341 SkRect bbox;
342 bbox.setBoundsNoCheck(pts, 4);
343 float worst = wangs_formula::worst_case_cubic(kPrecision, bbox.width(), bbox.height());
344 int worst_log2 = wangs_formula::worst_case_cubic_log2(kPrecision, bbox.width(),
345 bbox.height());
346 float actual = wangs_formula_cubic_reference_impl(kPrecision, pts);
347 REPORTER_ASSERT(r, worst >= actual);
348 REPORTER_ASSERT(r, std::ceil(std::log2(std::max(1.f, worst))) == worst_log2);
349 };
350 SkRandom rand;
351 for (int i = 0; i < 100; ++i) {
352 for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
353 check_worst_case_cubic(pts);
354 });
355 }
356 // Make sure overflow saturates at infinity (not NaN).
357 constexpr static float inf = std::numeric_limits<float>::infinity();
358 REPORTER_ASSERT(r, wangs_formula::worst_case_cubic_p4(kPrecision, inf, inf) == inf);
359 REPORTER_ASSERT(r, wangs_formula::worst_case_cubic(kPrecision, inf, inf) == inf);
360}
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762
void setBoundsNoCheck(const SkPoint pts[], int count)
Definition SkRect.cpp:100

◆ FindCubicConvex180Chops()

int skgpu::tess::FindCubicConvex180Chops ( const SkPoint  pts[],
float  T[2],
bool *  areCusps 
)

Definition at line 205 of file Tessellation.cpp.

205 {
206 SkASSERT(pts);
207 SkASSERT(T);
208 SkASSERT(areCusps);
209
210 // If a chop falls within a distance of "kEpsilon" from 0 or 1, throw it out. Tangents become
211 // unstable when we chop too close to the boundary. This works out because the tessellation
212 // shaders don't allow more than 2^10 parametric segments, and they snap the beginning and
213 // ending edges at 0 and 1. So if we overstep an inflection or point of 180-degree rotation by a
214 // fraction of a tessellation segment, it just gets snapped.
215 constexpr static float kEpsilon = 1.f / (1 << 11);
216 // Floating-point representation of "1 - 2*kEpsilon".
217 constexpr static uint32_t kIEEE_one_minus_2_epsilon = (127 << 23) - 2 * (1 << (24 - 11));
218 // Unfortunately we don't have a way to static_assert this, but we can runtime assert that the
219 // kIEEE_one_minus_2_epsilon bits are correct.
220 SkASSERT(sk_bit_cast<float>(kIEEE_one_minus_2_epsilon) == 1 - 2*kEpsilon);
221
222 float2 p0 = sk_bit_cast<float2>(pts[0]);
223 float2 p1 = sk_bit_cast<float2>(pts[1]);
224 float2 p2 = sk_bit_cast<float2>(pts[2]);
225 float2 p3 = sk_bit_cast<float2>(pts[3]);
226
227 // Find the cubic's power basis coefficients. These define the bezier curve as:
228 //
229 // |T^3|
230 // Cubic(T) = x,y = |A 3B 3C| * |T^2| + P0
231 // |. . .| |T |
232 //
233 // And the tangent direction (scaled by a uniform 1/3) will be:
234 //
235 // |T^2|
236 // Tangent_Direction(T) = dx,dy = |A 2B C| * |T |
237 // |. . .| |1 |
238 //
239 float2 C = p1 - p0;
240 float2 D = p2 - p1;
241 float2 E = p3 - p0;
242 float2 B = D - C;
243 float2 A = -3*D + E;
244
245 // Now find the cubic's inflection function. There are inflections where F' x F'' == 0.
246 // We formulate this as a quadratic equation: F' x F'' == aT^2 + bT + c == 0.
247 // See: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
248 // NOTE: We only need the roots, so a uniform scale factor does not affect the solution.
249 float a = cross(A,B);
250 float b = cross(A,C);
251 float c = cross(B,C);
252 float b_over_minus_2 = -.5f * b;
253 float discr_over_4 = b_over_minus_2*b_over_minus_2 - a*c;
254
255 // If -cuspThreshold <= discr_over_4 <= cuspThreshold, it means the two roots are within
256 // kEpsilon of one another (in parametric space). This is close enough for our purposes to
257 // consider them a single cusp.
258 float cuspThreshold = a * (kEpsilon/2);
259 cuspThreshold *= cuspThreshold;
260
261 if (discr_over_4 < -cuspThreshold) {
262 // The curve does not inflect or cusp. This means it might rotate more than 180 degrees
263 // instead. Chop were rotation == 180 deg. (This is the 2nd root where the tangent is
264 // parallel to tan0.)
265 //
266 // Tangent_Direction(T) x tan0 == 0
267 // (AT^2 x tan0) + (2BT x tan0) + (C x tan0) == 0
268 // (A x C)T^2 + (2B x C)T + (C x C) == 0 [[because tan0 == P1 - P0 == C]]
269 // bT^2 + 2cT + 0 == 0 [[because A x C == b, B x C == c]]
270 // T = [0, -2c/b]
271 //
272 // NOTE: if C == 0, then C != tan0. But this is fine because the curve is definitely
273 // convex-180 if any points are colocated, and T[0] will equal NaN which returns 0 chops.
274 *areCusps = false;
275 float root = sk_ieee_float_divide(c, b_over_minus_2);
276 // Is "root" inside the range [kEpsilon, 1 - kEpsilon)?
277 if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
278 T[0] = root;
279 return 1;
280 }
281 return 0;
282 }
283
284 *areCusps = (discr_over_4 <= cuspThreshold);
285 if (*areCusps) {
286 // The two roots are close enough that we can consider them a single cusp.
287 if (a != 0 || b_over_minus_2 != 0 || c != 0) {
288 // Pick the average of both roots.
289 float root = sk_ieee_float_divide(b_over_minus_2, a);
290 // Is "root" inside the range [kEpsilon, 1 - kEpsilon)?
291 if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
292 T[0] = root;
293 return 1;
294 }
295 return 0;
296 }
297
298 // The curve is a flat line. The standard inflection function doesn't detect cusps from flat
299 // lines. Find cusps by searching instead for points where the tangent is perpendicular to
300 // tan0. This will find any cusp point.
301 //
302 // dot(tan0, Tangent_Direction(T)) == 0
303 //
304 // |T^2|
305 // tan0 * |A 2B C| * |T | == 0
306 // |. . .| |1 |
307 //
308 float2 tan0 = skvx::if_then_else(C != 0, C, p2 - p0);
309 a = dot(tan0, A);
310 b_over_minus_2 = -dot(tan0, B);
311 c = dot(tan0, C);
312 discr_over_4 = std::max(b_over_minus_2*b_over_minus_2 - a*c, 0.f);
313 }
314
315 // Solve our quadratic equation to find where to chop. See the quadratic formula from
316 // Numerical Recipes in C.
317 float q = sqrtf(discr_over_4);
318 q = copysignf(q, b_over_minus_2);
319 q = q + b_over_minus_2;
320 float2 roots = float2{q,c} / float2{a,q};
321
322 auto inside = (roots > kEpsilon) & (roots < (1 - kEpsilon));
323 if (inside[0]) {
324 if (inside[1] && roots[0] != roots[1]) {
325 if (roots[0] > roots[1]) {
326 roots = skvx::shuffle<1,0>(roots); // Sort.
327 }
328 roots.store(T);
329 return 2;
330 }
331 T[0] = roots[0];
332 return 1;
333 }
334 if (inside[1]) {
335 T[0] = roots[1];
336 return 1;
337 }
338 return 0;
339}
static constexpr float sk_ieee_float_divide(float numer, float denom)
SIT Vec< 1, T > if_then_else(const Vec< 1, M< T > > &cond, const Vec< 1, T > &t, const Vec< 1, T > &e)
Definition SkVx.h:479

◆ for_random_beziers()

static void skgpu::tess::for_random_beziers ( int  numPoints,
SkRandom rand,
const std::function< void(const SkPoint[])> &  f,
int  maxExponent = 30 
)
static

Definition at line 115 of file WangsFormulaTest.cpp.

117 {
118 SkASSERT(numPoints <= 4);
119 SkPoint pts[4];
120 for (int i = -10; i <= maxExponent; ++i) {
121 for (int j = 0; j < numPoints; ++j) {
122 pts[j].set(std::ldexp(1 + rand->nextF(), i), std::ldexp(1 + rand->nextF(), i));
123 }
124 f(pts);
125 }
126}
void set(float x, float y)

◆ for_random_matrices()

static void skgpu::tess::for_random_matrices ( SkRandom rand,
const std::function< void(const SkMatrix &)> &  f 
)
static

Definition at line 93 of file WangsFormulaTest.cpp.

93 {
94 SkMatrix m;
95 m.setIdentity();
96 f(m);
97
98 for (int i = -10; i <= 30; ++i) {
99 for (int j = -10; j <= 30; ++j) {
100 m.setScaleX(std::ldexp(1 + rand->nextF(), i));
101 m.setSkewX(0);
102 m.setSkewY(0);
103 m.setScaleY(std::ldexp(1 + rand->nextF(), j));
104 f(m);
105
106 m.setScaleX(std::ldexp(1 + rand->nextF(), i));
107 m.setSkewX(std::ldexp(1 + rand->nextF(), (j + i) / 2));
108 m.setSkewY(std::ldexp(1 + rand->nextF(), (j + i) / 2));
109 m.setScaleY(std::ldexp(1 + rand->nextF(), j));
110 f(m);
111 }
112 }
113}

◆ GetJoinType()

float skgpu::tess::GetJoinType ( const SkStrokeRec stroke)
inline

Definition at line 146 of file Tessellation.h.

146 {
147 switch (stroke.getJoin()) {
148 case SkPaint::kRound_Join: return -1;
149 case SkPaint::kBevel_Join: return 0;
150 case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter();
151 }
153}
#define SkUNREACHABLE
Definition SkAssert.h:135
@ kRound_Join
adds circle
Definition SkPaint.h:360
@ kMiter_Join
extends to miter limit
Definition SkPaint.h:359
@ kBevel_Join
connects outside edges
Definition SkPaint.h:361
SkPaint::Join getJoin() const
Definition SkStrokeRec.h:45
SkScalar getMiter() const
Definition SkStrokeRec.h:43

◆ is_linear() [1/2]

static bool skgpu::tess::is_linear ( const SkPoint  p[4])
static

Definition at line 25 of file FindCubicConvex180ChopsTest.cpp.

25 {
26 return is_linear(p[0],p[1],p[2]) && is_linear(p[0],p[2],p[3]) && is_linear(p[1],p[2],p[3]);
27}

◆ is_linear() [2/2]

static bool skgpu::tess::is_linear ( SkPoint  p0,
SkPoint  p1,
SkPoint  p2 
)
static

Definition at line 21 of file FindCubicConvex180ChopsTest.cpp.

21 {
22 return SkScalarNearlyZero((p0 - p1).cross(p2 - p1));
23}
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101

◆ NumCurveTrianglesAtResolveLevel()

static constexpr int skgpu::tess::NumCurveTrianglesAtResolveLevel ( int  resolveLevel)
staticconstexpr

Definition at line 66 of file Tessellation.h.

66 {
67 // resolveLevel=0 -> 0 line segments -> 0 triangles
68 // resolveLevel=1 -> 2 line segments -> 1 triangle
69 // resolveLevel=2 -> 4 line segments -> 3 triangles
70 // resolveLevel=3 -> 8 line segments -> 7 triangles
71 // ...
72 return (1 << resolveLevel) - 1;
73}

◆ NumFixedEdgesInJoin() [1/2]

constexpr int skgpu::tess::NumFixedEdgesInJoin ( const StrokeParams strokeParams)
constexpr

Definition at line 202 of file Tessellation.h.

202 {
203 // The caller is responsible for counting the variable number of segments for round joins.
204 return strokeParams.fJoinType > 0.f ? /* miter */ 4 : /* round or bevel */ 3;
205}

◆ NumFixedEdgesInJoin() [2/2]

constexpr int skgpu::tess::NumFixedEdgesInJoin ( SkPaint::Join  joinType)
constexpr

Definition at line 189 of file Tessellation.h.

189 {
190 switch (joinType) {
192 return 4;
194 // The caller is responsible for counting the variable number of middle, radial
195 // segments on round joins.
196 [[fallthrough]];
198 return 3;
199 }
201}

◆ operator<<()

template<PatchAttribs A, typename T , bool Required, bool Optional>
VertexWriter & skgpu::tess::operator<< ( VertexWriter w,
const AttribValue< A, T, Required, Optional > &  v 
)

Definition at line 168 of file PatchWriter.h.

168 {
169 if constexpr (Required) {
170 w << v.fV; // always write
171 } else if constexpr (Optional) {
172 if (std::get<1>(v.fV)) {
173 w << std::get<0>(v.fV); // write if enabled
174 }
175 } // else never write
176 return w;
177}

◆ PatchAttribsStride()

constexpr size_t skgpu::tess::PatchAttribsStride ( PatchAttribs  attribs)
constexpr

Definition at line 103 of file Tessellation.h.

103 {
104 return (attribs & PatchAttribs::kJoinControlPoint ? sizeof(float) * 2 : 0) +
105 (attribs & PatchAttribs::kFanPoint ? sizeof(float) * 2 : 0) +
106 (attribs & PatchAttribs::kStrokeParams ? sizeof(float) * 2 : 0) +
107 (attribs & PatchAttribs::kColor
108 ? (attribs & PatchAttribs::kWideColorIfEnabled ? sizeof(float)
109 : sizeof(uint8_t)) * 4 : 0) +
110 (attribs & PatchAttribs::kPaintDepth ? sizeof(float) : 0) +
111 (attribs & PatchAttribs::kExplicitCurveType ? sizeof(float) : 0) +
112 (attribs & PatchAttribs::kSsboIndex ? (sizeof(int)) : 0);
113}
Type::kYUV Type::kRGBA() int(0.7 *637)

◆ PatchStride()

constexpr size_t skgpu::tess::PatchStride ( PatchAttribs  attribs)
constexpr

Definition at line 114 of file Tessellation.h.

114 {
115 return 4*sizeof(SkPoint) + PatchAttribsStride(attribs);
116}
constexpr size_t PatchAttribsStride(PatchAttribs attribs)

◆ PreChopPathCurves()

SkPath skgpu::tess::PreChopPathCurves ( float  tessellationPrecision,
const SkPath path,
const SkMatrix matrix,
const SkRect viewport 
)

Definition at line 164 of file Tessellation.cpp.

167 {
168 // If the viewport is exceptionally large, we could end up blowing out memory with an unbounded
169 // number of of chops. Therefore, we require that the viewport is manageable enough that a fully
170 // contained curve can be tessellated in kMaxTessellationSegmentsPerCurve or fewer. (Any larger
171 // and that amount of pixels wouldn't fit in memory anyway.)
172 SkASSERT(wangs_formula::worst_case_cubic(
173 tessellationPrecision,
174 viewport.width(),
175 viewport.height()) <= kMaxSegmentsPerCurve);
176 PathChopper chopper(tessellationPrecision, matrix, viewport);
177 for (auto [verb, p, w] : SkPathPriv::Iterate(path)) {
178 switch (verb) {
180 chopper.moveTo(p[0]);
181 break;
183 chopper.lineTo(p);
184 break;
186 chopper.quadTo(p);
187 break;
189 chopper.conicTo(p, *w);
190 break;
192 chopper.cubicTo(p);
193 break;
195 chopper.close();
196 break;
197 }
198 }
199 // Must preserve the input path's fill type (see crbug.com/1472747)
200 SkPath chopped = chopper.path();
201 chopped.setFillType(path.getFillType());
202 return chopped;
203}
@ kClose
SkPath::RawIter returns 0 points.
@ kCubic
SkPath::RawIter returns 4 points.
@ kConic
SkPath::RawIter returns 3 points + 1 weight.
@ kQuad
SkPath::RawIter returns 3 points.
@ kMove
SkPath::RawIter returns 1 point.
@ kLine
SkPath::RawIter returns 2 points.
void setFillType(SkPathFillType ft)
Definition SkPath.h:235

◆ StrokesHaveEqualParams()

bool skgpu::tess::StrokesHaveEqualParams ( const SkStrokeRec a,
const SkStrokeRec b 
)
inline

Definition at line 171 of file Tessellation.h.

171 {
172 return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() &&
173 (a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter());
174}

◆ wangs_formula_conic_reference_impl()

static float skgpu::tess::wangs_formula_conic_reference_impl ( float  precision,
const SkPoint  P[3],
const float  w 
)
static

Definition at line 56 of file WangsFormulaTest.cpp.

58 {
59 // Compute center of bounding box in projected space
60 float min_x = P[0].fX, max_x = min_x,
61 min_y = P[0].fY, max_y = min_y;
62 for (int i = 1; i < 3; i++) {
63 min_x = std::min(min_x, P[i].fX);
64 max_x = std::max(max_x, P[i].fX);
65 min_y = std::min(min_y, P[i].fY);
66 max_y = std::max(max_y, P[i].fY);
67 }
68 const SkPoint C = SkPoint::Make(0.5f * (min_x + max_x), 0.5f * (min_y + max_y));
69
70 // Translate control points and compute max length
71 SkPoint tP[3] = {P[0] - C, P[1] - C, P[2] - C};
72 float max_len = 0;
73 for (int i = 0; i < 3; i++) {
74 max_len = std::max(max_len, tP[i].length());
75 }
76 SkASSERT(max_len > 0);
77
78 // Compute delta = parametric step size of linearization
79 const float eps = 1 / precision;
80 const float r_minus_eps = std::max(0.f, max_len - eps);
81 const float min_w = std::min(w, 1.f);
82 const float numer = 4 * min_w * eps;
83 const float denom =
84 (tP[2] - tP[1] * 2 * w + tP[0]).length() + r_minus_eps * std::abs(1 - 2 * w + 1);
85 const float delta = sqrtf(numer / denom);
86
87 // Return corresponding num segments in the interval [tmin,tmax]
88 constexpr float tmin = 0, tmax = 1;
89 SkASSERT(delta > 0);
90 return (tmax - tmin) / delta;
91}

◆ wangs_formula_cubic_reference_impl()

static float skgpu::tess::wangs_formula_cubic_reference_impl ( float  precision,
const SkPoint  p[4] 
)
static

Definition at line 42 of file WangsFormulaTest.cpp.

42 {
43 float k = (3 * 2) / 8.f * precision;
44 return sqrtf(k * std::max((p[0] - p[1]*2 + p[2]).length(),
45 (p[1] - p[2]*2 + p[3]).length()));
46}

◆ wangs_formula_quadratic_reference_impl()

static float skgpu::tess::wangs_formula_quadratic_reference_impl ( float  precision,
const SkPoint  p[3] 
)
static

Definition at line 37 of file WangsFormulaTest.cpp.

37 {
38 float k = (2 * 1) / 8.f * precision;
39 return sqrtf(k * (p[0] - p[1]*2 + p[2]).length());
40}

Variable Documentation

◆ gMatrices

const SkMatrix skgpu::tess::gMatrices[]
Initial value:
= {
SkMatrix::Translate(25, -1000),
SkMatrix::Scale(.5f, 1000.1f),
SkMatrix::MakeAll(1000.1f, .0f, -100,
0.0f, .5f, -3000,
0.0f, .0f, 1),
1, 0, 0,
0, 0, 1),
SkMatrix::MakeAll( 2, 7.0f, -100,
-8000, .5f, 2000,
0, .0f, 1),
}
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
static SkMatrix MakeAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar pers0, SkScalar pers1, SkScalar pers2)
Definition SkMatrix.h:179

Definition at line 20 of file CullTestTest.cpp.

20 {
22 SkMatrix::Translate(25, -1000),
23 SkMatrix::Scale(.5f, 1000.1f),
24 SkMatrix::MakeAll(1000.1f, .0f, -100,
25 0.0f, .5f, -3000,
26 0.0f, .0f, 1),
27 SkMatrix::MakeAll(0, 1, 0,
28 1, 0, 0,
29 0, 0, 1),
30 SkMatrix::MakeAll( 2, 7.0f, -100,
31 -8000, .5f, 2000,
32 0, .0f, 1),
33};

◆ kConicCurveType

constexpr float skgpu::tess::kConicCurveType = 1
staticconstexpr

Definition at line 98 of file Tessellation.h.

◆ kCubicCurveType

constexpr float skgpu::tess::kCubicCurveType = 0
staticconstexpr

Definition at line 97 of file Tessellation.h.

◆ kLoop

const SkPoint skgpu::tess::kLoop[4]
Initial value:
= {
{635.625f, 614.687f}, {171.625f, 236.188f}, {1064.62f, 135.688f}, {516.625f, 570.187f}}

Definition at line 31 of file WangsFormulaTest.cpp.

31 {
32 {635.625f, 614.687f}, {171.625f, 236.188f}, {1064.62f, 135.688f}, {516.625f, 570.187f}};

◆ kMaxParametricSegments

constexpr int skgpu::tess::kMaxParametricSegments = 1 << kMaxResolveLevel
staticconstexpr

Definition at line 41 of file Tessellation.h.

◆ kMaxParametricSegments_p2

constexpr int skgpu::tess::kMaxParametricSegments_p2 = kMaxParametricSegments * kMaxParametricSegments
staticconstexpr

Definition at line 42 of file Tessellation.h.

◆ kMaxParametricSegments_p4

constexpr int skgpu::tess::kMaxParametricSegments_p4
staticconstexpr
Initial value:

Definition at line 43 of file Tessellation.h.

◆ kMaxResolveLevel

constexpr int skgpu::tess::kMaxResolveLevel = 5
staticconstexpr

Definition at line 34 of file Tessellation.h.

◆ kMaxSegmentsPerCurve

constexpr float skgpu::tess::kMaxSegmentsPerCurve = 1024
staticconstexpr

Definition at line 51 of file Tessellation.h.

◆ kMaxSegmentsPerCurve_p2

constexpr float skgpu::tess::kMaxSegmentsPerCurve_p2 = kMaxSegmentsPerCurve * kMaxSegmentsPerCurve
staticconstexpr

Definition at line 52 of file Tessellation.h.

◆ kMaxSegmentsPerCurve_p4

constexpr float skgpu::tess::kMaxSegmentsPerCurve_p4 = kMaxSegmentsPerCurve_p2 * kMaxSegmentsPerCurve_p2
staticconstexpr

Definition at line 53 of file Tessellation.h.

◆ kPrecision

constexpr float skgpu::tess::kPrecision = 4
staticconstexpr

Definition at line 29 of file Tessellation.h.

◆ kQuad

const SkPoint skgpu::tess::kQuad[4]
Initial value:
= {
{460.625f, 557.187f}, {707.121f, 209.688f}, {779.628f, 577.687f}}

Definition at line 34 of file WangsFormulaTest.cpp.

34 {
35 {460.625f, 557.187f}, {707.121f, 209.688f}, {779.628f, 577.687f}};

◆ kSerp

const SkPoint skgpu::tess::kSerp[4]
Initial value:
= {
{285.625f, 499.687f}, {411.625f, 808.188f}, {1064.62f, 135.688f}, {1042.63f, 585.187f}}

Definition at line 28 of file WangsFormulaTest.cpp.

28 {
29 {285.625f, 499.687f}, {411.625f, 808.188f}, {1064.62f, 135.688f}, {1042.63f, 585.187f}};

◆ kTriangularConicCurveType

constexpr float skgpu::tess::kTriangularConicCurveType = 2
staticconstexpr

Definition at line 99 of file Tessellation.h.