• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "tests/Test.h"
9 
10 #include "include/private/SkFloatingPoint.h"
11 #include "src/core/SkGeometry.h"
12 #include "src/gpu/geometry/GrPathUtils.h"
13 #include "src/gpu/geometry/GrWangsFormula.h"
14 #include "src/gpu/mock/GrMockOpTarget.h"
15 #include "src/gpu/tessellate/GrStrokeIndirectTessellator.h"
16 #include "src/gpu/tessellate/GrStrokeTessellateShader.h"
17 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
18 
make_mock_context()19 static sk_sp<GrDirectContext> make_mock_context() {
20     GrMockOptions mockOptions;
21     mockOptions.fDrawInstancedSupport = true;
22     mockOptions.fMaxTessellationSegments = 64;
23     mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag;
24     mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability =
25             GrMockOptions::ConfigOptions::Renderability::kMSAA;
26     mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true;
27     mockOptions.fIntegerSupport = true;
28 
29     GrContextOptions ctxOptions;
30     ctxOptions.fGpuPathRenderers = GpuPathRenderers::kTessellation;
31 
32     return GrDirectContext::MakeMock(&mockOptions, ctxOptions);
33 }
34 
test_stroke(skiatest::Reporter * r,GrDirectContext * ctx,GrMockOpTarget * target,const SkPath & path,SkRandom & rand)35 static void test_stroke(skiatest::Reporter* r, GrDirectContext* ctx, GrMockOpTarget* target,
36                         const SkPath& path, SkRandom& rand) {
37     SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
38     stroke.setStrokeStyle(.1f);
39     for (auto join : {SkPaint::kMiter_Join, SkPaint::kRound_Join}) {
40         stroke.setStrokeParams(SkPaint::kButt_Cap, join, 4);
41         for (int i = 0; i < 16; ++i) {
42             float scale = ldexpf(rand.nextF() + 1, i);
43             auto matrix = SkMatrix::Scale(scale, scale);
44             GrStrokeTessellator::PathStrokeList pathStrokeList(path, stroke, SK_PMColor4fWHITE);
45             GrStrokeIndirectTessellator tessellator(GrStrokeTessellateShader::ShaderFlags::kNone,
46                                                     matrix, &pathStrokeList, path.countVerbs(),
47                                                     target->allocator());
48             tessellator.verifyResolveLevels(r, target, matrix, path, stroke);
49             tessellator.prepare(target, path.countVerbs());
50             tessellator.verifyBuffers(r, target, matrix, stroke);
51         }
52     }
53 }
54 
DEF_TEST(tessellate_GrStrokeIndirectTessellator,r)55 DEF_TEST(tessellate_GrStrokeIndirectTessellator, r) {
56     auto ctx = make_mock_context();
57     auto target = std::make_unique<GrMockOpTarget>(ctx);
58     SkRandom rand;
59 
60     // Empty strokes.
61     SkPath path = SkPath();
62     test_stroke(r, ctx.get(), target.get(), path, rand);
63     path.moveTo(1,1);
64     test_stroke(r, ctx.get(), target.get(), path, rand);
65     path.moveTo(1,1);
66     test_stroke(r, ctx.get(), target.get(), path, rand);
67     path.close();
68     test_stroke(r, ctx.get(), target.get(), path, rand);
69     path.moveTo(1,1);
70     test_stroke(r, ctx.get(), target.get(), path, rand);
71 
72     // Single line.
73     path = SkPath().lineTo(1,1);
74     test_stroke(r, ctx.get(), target.get(), path, rand);
75     path.close();
76     test_stroke(r, ctx.get(), target.get(), path, rand);
77 
78     // Single quad.
79     path = SkPath().quadTo(1,0,1,1);
80     test_stroke(r, ctx.get(), target.get(), path, rand);
81     path.close();
82     test_stroke(r, ctx.get(), target.get(), path, rand);
83 
84     // Single cubic.
85     path = SkPath().cubicTo(1,0,0,1,1,1);
86     test_stroke(r, ctx.get(), target.get(), path, rand);
87     path.close();
88     test_stroke(r, ctx.get(), target.get(), path, rand);
89 
90     // All types of lines.
91     path.reset();
92     for (int i = 0; i < (1 << 4); ++i) {
93         path.moveTo((i>>0)&1, (i>>1)&1);
94         path.lineTo((i>>2)&1, (i>>3)&1);
95         path.close();
96     }
97     test_stroke(r, ctx.get(), target.get(), path, rand);
98 
99     // All types of quads.
100     path.reset();
101     for (int i = 0; i < (1 << 6); ++i) {
102         path.moveTo((i>>0)&1, (i>>1)&1);
103         path.quadTo((i>>2)&1, (i>>3)&1, (i>>4)&1, (i>>5)&1);
104         path.close();
105     }
106     test_stroke(r, ctx.get(), target.get(), path, rand);
107 
108     // All types of cubics.
109     path.reset();
110     for (int i = 0; i < (1 << 8); ++i) {
111         path.moveTo((i>>0)&1, (i>>1)&1);
112         path.cubicTo((i>>2)&1, (i>>3)&1, (i>>4)&1, (i>>5)&1, (i>>6)&1, (i>>7)&1);
113         path.close();
114     }
115     test_stroke(r, ctx.get(), target.get(), path, rand);
116 
117     {
118         // This cubic has a convex-180 chop at T=1-"epsilon"
119         static const uint32_t hexPts[] = {0x3ee0ac74, 0x3f1e061a, 0x3e0fc408, 0x3f457230,
120                                           0x3f42ac7c, 0x3f70d76c, 0x3f4e6520, 0x3f6acafa};
121         SkPoint pts[4];
122         memcpy(pts, hexPts, sizeof(pts));
123         test_stroke(r, ctx.get(), target.get(),
124                     SkPath().moveTo(pts[0]).cubicTo(pts[1], pts[2], pts[3]).close(), rand);
125     }
126 
127     // Random paths.
128     for (int j = 0; j < 50; ++j) {
129         path.reset();
130         // Empty contours behave differently if closed.
131         path.moveTo(0,0);
132         path.moveTo(0,0);
133         path.close();
134         path.moveTo(0,0);
135         SkPoint startPoint = {rand.nextF(), rand.nextF()};
136         path.moveTo(startPoint);
137         // Degenerate curves get skipped.
138         path.lineTo(startPoint);
139         path.quadTo(startPoint, startPoint);
140         path.cubicTo(startPoint, startPoint, startPoint);
141         for (int i = 0; i < 100; ++i) {
142             switch (rand.nextRangeU(0, 4)) {
143                 case 0:
144                     path.lineTo(rand.nextF(), rand.nextF());
145                     break;
146                 case 1:
147                     path.quadTo(rand.nextF(), rand.nextF(), rand.nextF(), rand.nextF());
148                     break;
149                 case 2:
150                 case 3:
151                 case 4:
152                     path.cubicTo(rand.nextF(), rand.nextF(), rand.nextF(), rand.nextF(),
153                                  rand.nextF(), rand.nextF());
154                     break;
155                 default:
156                     SkUNREACHABLE;
157             }
158             if (i % 19 == 0) {
159                 switch (i/19 % 4) {
160                     case 0:
161                         break;
162                     case 1:
163                         path.lineTo(startPoint);
164                         break;
165                     case 2:
166                         path.quadTo(SkPoint::Make(1.1f, 1.1f), startPoint);
167                         break;
168                     case 3:
169                         path.cubicTo(SkPoint::Make(1.1f, 1.1f), SkPoint::Make(1.1f, 1.1f),
170                                      startPoint);
171                         break;
172                 }
173                 path.close();
174                 if (rand.nextU() & 1) {  // Implicit or explicit move?
175                     startPoint = {rand.nextF(), rand.nextF()};
176                     path.moveTo(startPoint);
177                 }
178             }
179         }
180         test_stroke(r, ctx.get(), target.get(), path, rand);
181     }
182 }
183 
184 // Returns the control point for the first/final join of a contour.
185 // If the contour is not closed, returns the start point.
get_contour_closing_control_point(SkPathPriv::RangeIter iter,const SkPathPriv::RangeIter & end)186 static SkPoint get_contour_closing_control_point(SkPathPriv::RangeIter iter,
187                                                  const SkPathPriv::RangeIter& end) {
188     auto [verb, p, w] = *iter;
189     SkASSERT(verb == SkPathVerb::kMove);
190     // Peek ahead to find the last control point.
191     SkPoint startPoint=p[0], lastControlPoint=p[0];
192     for (++iter; iter != end; ++iter) {
193         auto [verb, p, w] = *iter;
194         switch (verb) {
195             case SkPathVerb::kMove:
196                 return startPoint;
197             case SkPathVerb::kCubic:
198                 if (p[2] != p[3]) {
199                     lastControlPoint = p[2];
200                     break;
201                 }
202                 [[fallthrough]];
203             case SkPathVerb::kQuad:
204                 if (p[1] != p[2]) {
205                     lastControlPoint = p[1];
206                     break;
207                 }
208                 [[fallthrough]];
209             case SkPathVerb::kLine:
210                 if (p[0] != p[1]) {
211                     lastControlPoint = p[0];
212                 }
213                 break;
214             case SkPathVerb::kConic:
215                 SkUNREACHABLE;
216             case SkPathVerb::kClose:
217                 return (p[0] == startPoint) ? lastControlPoint : p[0];
218         }
219     }
220     return startPoint;
221 }
222 
check_resolve_level(skiatest::Reporter * r,float numCombinedSegments,int8_t actualLevel,float tolerance,bool printError=true)223 static bool check_resolve_level(skiatest::Reporter* r,  float numCombinedSegments,
224                                 int8_t actualLevel, float tolerance, bool printError = true) {
225     int8_t expectedLevel = sk_float_nextlog2(numCombinedSegments);
226     if ((actualLevel > expectedLevel &&
227          actualLevel > sk_float_nextlog2(numCombinedSegments + tolerance)) ||
228         (actualLevel < expectedLevel &&
229          actualLevel < sk_float_nextlog2(numCombinedSegments - tolerance))) {
230         if (printError) {
231             ERRORF(r, "expected %f segments => resolveLevel=%i (got %i)\n",
232                    numCombinedSegments, expectedLevel, actualLevel);
233         }
234         return false;
235     }
236     return true;
237 }
238 
check_first_resolve_levels(skiatest::Reporter * r,const SkTArray<float> & firstNumSegments,int8_t ** nextResolveLevel,float tolerance)239 static bool check_first_resolve_levels(skiatest::Reporter* r,
240                                        const SkTArray<float>& firstNumSegments,
241                                        int8_t** nextResolveLevel, float tolerance) {
242     for (float numSegments : firstNumSegments) {
243         if (numSegments < 0) {
244             int8_t val = *(*nextResolveLevel)++;
245             REPORTER_ASSERT(r, val == (int)numSegments);
246             continue;
247         }
248         // The first stroke's resolve levels aren't  written out until the end of
249         // the contour.
250         if (!check_resolve_level(r, numSegments, *(*nextResolveLevel)++, tolerance)) {
251             return false;
252         }
253     }
254     return true;
255 }
256 
test_tolerance(SkPaint::Join joinType)257 static float test_tolerance(SkPaint::Join joinType) {
258     // Ensure our fast approximation falls within 1.15 tessellation segments of the "correct"
259     // answer. This is more than good enough when our matrix scale can go up to 2^17.
260     float tolerance = 1.15f;
261     if (joinType == SkPaint::kRound_Join) {
262         // We approximate two different angles when there are round joins. Double the tolerance.
263         tolerance *= 2;
264     }
265     return tolerance;
266 }
267 
verifyResolveLevels(skiatest::Reporter * r,GrMockOpTarget * target,const SkMatrix & viewMatrix,const SkPath & path,const SkStrokeRec & stroke)268 void GrStrokeIndirectTessellator::verifyResolveLevels(skiatest::Reporter* r,
269                                                       GrMockOpTarget* target,
270                                                       const SkMatrix& viewMatrix,
271                                                       const SkPath& path,
272                                                       const SkStrokeRec& stroke) {
273     auto tolerances = GrStrokeTolerances::MakeNonHairline(viewMatrix.getMaxScale(),
274                                                           stroke.getWidth());
275     int8_t resolveLevelForCircles = SkTPin<float>(
276             sk_float_nextlog2(tolerances.fNumRadialSegmentsPerRadian * SK_ScalarPI),
277             1, kMaxResolveLevel);
278     float tolerance = test_tolerance(stroke.getJoin());
279     int8_t* nextResolveLevel = fResolveLevels;
280     auto iterate = SkPathPriv::Iterate(path);
281     SkSTArray<3, float> firstNumSegments;
282     bool isFirstStroke = true;
283     SkPoint startPoint = {0,0};
284     SkPoint lastControlPoint;
285     for (auto iter = iterate.begin(); iter != iterate.end(); ++iter) {
286         auto [verb, pts, w] = *iter;
287         switch (verb) {
288             int n;
289             SkPoint chops[10];
290             case SkPathVerb::kMove:
291                 startPoint = pts[0];
292                 lastControlPoint = get_contour_closing_control_point(iter, iterate.end());
293                 if (!check_first_resolve_levels(r, firstNumSegments, &nextResolveLevel,
294                                                 tolerance)) {
295                     return;
296                 }
297                 firstNumSegments.reset();
298                 isFirstStroke = true;
299                 break;
300             case SkPathVerb::kLine:
301                 if (pts[0] == pts[1]) {
302                     break;
303                 }
304                 if (stroke.getJoin() == SkPaint::kRound_Join) {
305                     float rotation = SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint,
306                                                                   pts[1] - pts[0]);
307                     float numSegments = rotation * tolerances.fNumRadialSegmentsPerRadian;
308                     if (isFirstStroke) {
309                         firstNumSegments.push_back(numSegments);
310                     } else if (!check_resolve_level(r, numSegments, *nextResolveLevel++,
311                                                     tolerance)) {
312                         return;
313                     }
314                 }
315                 lastControlPoint = pts[0];
316                 isFirstStroke = false;
317                 break;
318             case SkPathVerb::kQuad: {
319                 if (pts[0] == pts[1] && pts[1] == pts[2]) {
320                     break;
321                 }
322                 SkVector a = pts[1] - pts[0];
323                 SkVector b = pts[2] - pts[1];
324                 bool hasCusp = (a.cross(b) == 0 && a.dot(b) < 0);
325                 if (hasCusp) {
326                     // The quad has a cusp. Make sure we wrote out a -resolveLevelForCircles.
327                     if (isFirstStroke) {
328                         firstNumSegments.push_back(-resolveLevelForCircles);
329                     } else {
330                         REPORTER_ASSERT(r, *nextResolveLevel++ == -resolveLevelForCircles);
331                     }
332                 }
333                 float numParametricSegments = (hasCusp) ? 0 : GrWangsFormula::quadratic(
334                         tolerances.fParametricPrecision, pts);
335                 float rotation = (hasCusp) ? 0 : SkMeasureQuadRotation(pts);
336                 if (stroke.getJoin() == SkPaint::kRound_Join) {
337                     SkVector controlPoint = (pts[0] == pts[1]) ? pts[2] : pts[1];
338                     rotation += SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint,
339                                                              controlPoint - pts[0]);
340                 }
341                 float numRadialSegments = rotation * tolerances.fNumRadialSegmentsPerRadian;
342                 float numSegments = numParametricSegments + numRadialSegments;
343                 if (!hasCusp || stroke.getJoin() == SkPaint::kRound_Join) {
344                     if (isFirstStroke) {
345                         firstNumSegments.push_back(numSegments);
346                     } else if (!check_resolve_level(r, numSegments, *nextResolveLevel++,
347                                                     tolerance)) {
348                         return;
349                     }
350                 }
351                 lastControlPoint = (pts[2] == pts[1]) ? pts[0] : pts[1];
352                 isFirstStroke = false;
353                 break;
354             }
355             case SkPathVerb::kCubic: {
356                 if (pts[0] == pts[1] && pts[1] == pts[2] && pts[2] == pts[3]) {
357                     break;
358                 }
359                 float T[2];
360                 bool areCusps = false;
361                 n = GrPathUtils::findCubicConvex180Chops(pts, T, &areCusps);
362                 SkChopCubicAt(pts, chops, T, n);
363                 if (n > 0) {
364                     int cuspResolveLevel = (areCusps) ? resolveLevelForCircles : 0;
365                     int signal = -((n << 4) | cuspResolveLevel);
366                     if (isFirstStroke) {
367                         firstNumSegments.push_back((float)signal);
368                     } else {
369                         REPORTER_ASSERT(r, *nextResolveLevel++ == signal);
370                     }
371                 }
372                 for (int i = 0; i <= n; ++i) {
373                     // Find the number of segments with our unoptimized approach and make sure
374                     // it matches the answer we got already.
375                     SkPoint* p = chops + i*3;
376                     float numParametricSegments =
377                             GrWangsFormula::cubic(tolerances.fParametricPrecision, p);
378                     SkVector tan0 =
379                             ((p[0] == p[1]) ? (p[1] == p[2]) ? p[3] : p[2] : p[1]) - p[0];
380                     SkVector tan1 =
381                             p[3] - ((p[3] == p[2]) ? (p[2] == p[1]) ? p[0] : p[1] : p[2]);
382                     float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
383                     if (i == 0 && stroke.getJoin() == SkPaint::kRound_Join) {
384                         rotation += SkMeasureAngleBetweenVectors(p[0] - lastControlPoint, tan0);
385                     }
386                     float numRadialSegments = rotation * tolerances.fNumRadialSegmentsPerRadian;
387                     float numSegments = numParametricSegments + numRadialSegments;
388                     if (isFirstStroke) {
389                         firstNumSegments.push_back(numSegments);
390                     } else if (!check_resolve_level(r, numSegments, *nextResolveLevel++,
391                                                     tolerance)) {
392                         return;
393                     }
394                 }
395                 lastControlPoint =
396                         (pts[3] == pts[2]) ? (pts[2] == pts[1]) ? pts[0] : pts[1] : pts[2];
397                 isFirstStroke = false;
398                 break;
399             }
400             case SkPathVerb::kConic:
401                 SkUNREACHABLE;
402             case SkPathVerb::kClose:
403                 if (pts[0] != startPoint) {
404                     SkASSERT(!isFirstStroke);
405                     if (stroke.getJoin() == SkPaint::kRound_Join) {
406                         // Line from pts[0] to startPoint, with a preceding join.
407                         float rotation = SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint,
408                                                                       startPoint - pts[0]);
409                         if (!check_resolve_level(
410                                 r, rotation * tolerances.fNumRadialSegmentsPerRadian,
411                                 *nextResolveLevel++, tolerance)) {
412                             return;
413                         }
414                     }
415                 }
416                 if (!check_first_resolve_levels(r, firstNumSegments, &nextResolveLevel,
417                                                 tolerance)) {
418                     return;
419                 }
420                 firstNumSegments.reset();
421                 isFirstStroke = true;
422                 break;
423         }
424     }
425     if (!check_first_resolve_levels(r, firstNumSegments, &nextResolveLevel, tolerance)) {
426         return;
427     }
428     firstNumSegments.reset();
429     SkASSERT(nextResolveLevel == fResolveLevels + fResolveLevelArrayCount);
430 }
431 
verifyBuffers(skiatest::Reporter * r,GrMockOpTarget * target,const SkMatrix & viewMatrix,const SkStrokeRec & stroke)432 void GrStrokeIndirectTessellator::verifyBuffers(skiatest::Reporter* r, GrMockOpTarget* target,
433                                                 const SkMatrix& viewMatrix,
434                                                 const SkStrokeRec& stroke) {
435     // Make sure the resolve level we assigned to each instance agrees with the actual data.
436     struct IndirectInstance {
437         SkPoint fPts[4];
438         SkPoint fLastControlPoint;
439         float fNumTotalEdges;
440     };
441     auto instance = static_cast<const IndirectInstance*>(target->peekStaticVertexData());
442     auto* indirect = static_cast<const GrDrawIndirectCommand*>(target->peekStaticIndirectData());
443     auto tolerances = GrStrokeTolerances::MakeNonHairline(viewMatrix.getMaxScale(),
444                                                           stroke.getWidth());
445     float tolerance = test_tolerance(stroke.getJoin());
446     for (int i = 0; i < fChainedDrawIndirectCount; ++i) {
447         int numExtraEdgesInJoin = (stroke.getJoin() == SkPaint::kMiter_Join) ? 4 : 3;
448         int numStrokeEdges = indirect->fVertexCount/2 - numExtraEdgesInJoin;
449         int numSegments = numStrokeEdges - 1;
450         bool isPow2 = !(numSegments & (numSegments - 1));
451         REPORTER_ASSERT(r, isPow2);
452         int resolveLevel = sk_float_nextlog2(numSegments);
453         REPORTER_ASSERT(r, 1 << resolveLevel == numSegments);
454         for (unsigned j = 0; j < indirect->fInstanceCount; ++j) {
455             SkASSERT(fabsf(instance->fNumTotalEdges) == indirect->fVertexCount/2);
456             const SkPoint* p = instance->fPts;
457             float numParametricSegments = GrWangsFormula::cubic(tolerances.fParametricPrecision, p);
458             float alternateNumParametricSegments = numParametricSegments;
459             if (p[0] == p[1] && p[2] == p[3]) {
460                 // We articulate lines as "p0,p0,p1,p1". This one might actually expect 0 parametric
461                 // segments.
462                 alternateNumParametricSegments = 0;
463             }
464             SkVector tan0 = ((p[0] == p[1]) ? (p[1] == p[2]) ? p[3] : p[2] : p[1]) - p[0];
465             SkVector tan1 = p[3] - ((p[3] == p[2]) ? (p[2] == p[1]) ? p[0] : p[1] : p[2]);
466             float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
467             // Negative fNumTotalEdges means the curve is a chop, and chops always get treated as a
468             // bevel join.
469             if (stroke.getJoin() == SkPaint::kRound_Join && instance->fNumTotalEdges > 0) {
470                 SkVector lastTangent = p[0] - instance->fLastControlPoint;
471                 rotation += SkMeasureAngleBetweenVectors(lastTangent, tan0);
472             }
473             // Degenerate strokes are a special case that actually mean the GPU should draw a cusp
474             // (i.e. circle).
475             if (p[0] == p[1] && p[1] == p[2] && p[2] == p[3]) {
476                 rotation = SK_ScalarPI;
477             }
478             float numRadialSegments = rotation * tolerances.fNumRadialSegmentsPerRadian;
479             float numSegments = numParametricSegments + numRadialSegments;
480             float alternateNumSegments = alternateNumParametricSegments + numRadialSegments;
481             if (!check_resolve_level(r, numSegments, resolveLevel, tolerance, false) &&
482                 !check_resolve_level(r, alternateNumSegments, resolveLevel, tolerance, true)) {
483                 return;
484             }
485             ++instance;
486         }
487         ++indirect;
488     }
489 }
490