• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 "samplecode/Sample.h"
9 
10 #include "src/gpu/geometry/GrQuad.h"
11 #include "src/gpu/ops/GrQuadPerEdgeAA.h"
12 
13 #include "include/core/SkCanvas.h"
14 #include "include/core/SkPaint.h"
15 #include "include/effects/SkDashPathEffect.h"
16 #include "include/pathops/SkPathOps.h"
17 
18 // Draw a line through the two points, outset by a fixed length in screen space
draw_extended_line(SkCanvas * canvas,const SkPaint paint,const SkPoint & p0,const SkPoint & p1)19 static void draw_extended_line(SkCanvas* canvas, const SkPaint paint,
20                               const SkPoint& p0, const SkPoint& p1) {
21     SkVector v = p1 - p0;
22     v.setLength(v.length() + 3.f);
23     canvas->drawLine(p1 - v, p0 + v, paint);
24 
25     // Draw normal vector too
26     SkPaint normalPaint = paint;
27     normalPaint.setPathEffect(nullptr);
28     normalPaint.setStrokeWidth(paint.getStrokeWidth() / 4.f);
29 
30     SkVector n = {v.fY, -v.fX};
31     n.setLength(.25f);
32     SkPoint m = (p0 + p1) * 0.5f;
33     canvas->drawLine(m, m + n, normalPaint);
34 }
35 
make_aa_line(const SkPoint & p0,const SkPoint & p1,bool aaOn,bool outset,SkPoint line[2])36 static void make_aa_line(const SkPoint& p0, const SkPoint& p1, bool aaOn,
37                          bool outset, SkPoint line[2]) {
38     SkVector n = {0.f, 0.f};
39     if (aaOn) {
40         SkVector v = p1 - p0;
41         n = outset ? SkVector::Make(v.fY, -v.fX) : SkVector::Make(-v.fY, v.fX);
42         n.setLength(0.5f);
43     }
44 
45     line[0] = p0 + n;
46     line[1] = p1 + n;
47 }
48 
49 // To the line through l0-l1, not capped at the end points of the segment
signed_distance(const SkPoint & p,const SkPoint & l0,const SkPoint & l1)50 static SkScalar signed_distance(const SkPoint& p, const SkPoint& l0, const SkPoint& l1) {
51     SkVector v = l1 - l0;
52     v.normalize();
53     SkVector n = {v.fY, -v.fX};
54     SkScalar c = -n.dot(l0);
55     return n.dot(p) + c;
56 }
57 
get_area_coverage(const bool edgeAA[4],const SkPoint corners[4],const SkPoint & point)58 static SkScalar get_area_coverage(const bool edgeAA[4], const SkPoint corners[4],
59                                   const SkPoint& point) {
60     SkPath shape;
61     shape.addPoly(corners, 4, true);
62     SkPath pixel;
63     pixel.addRect(SkRect::MakeXYWH(point.fX - 0.5f, point.fY - 0.5f, 1.f, 1.f));
64 
65     SkPath intersection;
66     if (!Op(shape, pixel, kIntersect_SkPathOp, &intersection) || intersection.isEmpty()) {
67         return 0.f;
68     }
69 
70     // Calculate area of the convex polygon
71     SkScalar area = 0.f;
72     for (int i = 0; i < intersection.countPoints(); ++i) {
73         SkPoint p0 = intersection.getPoint(i);
74         SkPoint p1 = intersection.getPoint((i + 1) % intersection.countPoints());
75         SkScalar det = p0.fX * p1.fY - p1.fX * p0.fY;
76         area += det;
77     }
78 
79     // Scale by 1/2, then take abs value (this area formula is signed based on point winding, but
80     // since it's convex, just make it positive).
81     area = SkScalarAbs(0.5f * area);
82 
83     // Now account for the edge AA. If the pixel center is outside of a non-AA edge, turn of its
84     // coverage. If the pixel only intersects non-AA edges, then set coverage to 1.
85     bool needsNonAA = false;
86     SkScalar edgeD[4];
87     for (int i = 0; i < 4; ++i) {
88         SkPoint e0 = corners[i];
89         SkPoint e1 = corners[(i + 1) % 4];
90         edgeD[i] = -signed_distance(point, e0, e1);
91         if (!edgeAA[i]) {
92             if (edgeD[i] < -1e-4f) {
93                 return 0.f; // Outside of non-AA line
94             }
95             needsNonAA = true;
96         }
97     }
98     // Otherwise inside the shape, so check if any AA edge exerts influence over nonAA
99     if (needsNonAA) {
100         for (int i = 0; i < 4; i++) {
101             if (edgeAA[i] && edgeD[i] < 0.5f) {
102                 needsNonAA = false;
103                 break;
104             }
105         }
106     }
107     return needsNonAA ? 1.f : area;
108 }
109 
110 // FIXME take into account max coverage properly,
get_edge_dist_coverage(const bool edgeAA[4],const SkPoint corners[4],const SkPoint outsetLines[8],const SkPoint insetLines[8],const SkPoint & point)111 static SkScalar get_edge_dist_coverage(const bool edgeAA[4], const SkPoint corners[4],
112                                        const SkPoint outsetLines[8], const SkPoint insetLines[8],
113                                        const SkPoint& point) {
114     bool flip = false;
115     // If the quad has been inverted, the original corners will not all be on the negative side of
116     // every outset line. When that happens, calculate coverage using the "inset" lines and flip
117     // the signed distance
118     for (int i = 0; i < 4; ++i) {
119         for (int j = 0; j < 4; ++j) {
120             SkScalar d = signed_distance(corners[i], outsetLines[j * 2], outsetLines[j * 2 + 1]);
121             if (d > 1e-4f) {
122                 flip = true;
123                 break;
124             }
125         }
126         if (flip) {
127             break;
128         }
129     }
130 
131     const SkPoint* lines = flip ? insetLines : outsetLines;
132 
133     SkScalar minCoverage = 1.f;
134     for (int i = 0; i < 4; ++i) {
135         // Multiply by negative 1 so that outside points have negative distances
136         SkScalar d = (flip ? 1 : -1) * signed_distance(point, lines[i * 2], lines[i * 2 + 1]);
137         if (!edgeAA[i] && d >= -1e-4f) {
138             d = 1.f;
139         }
140         if (d < minCoverage) {
141             minCoverage = d;
142             if (minCoverage < 0.f) {
143                 break; // Outside the shape
144             }
145         }
146     }
147     return minCoverage < 0.f ? 0.f : minCoverage;
148 }
149 
inside_triangle(const SkPoint & point,const SkPoint & t0,const SkPoint & t1,const SkPoint & t2,SkScalar bary[3])150 static bool inside_triangle(const SkPoint& point, const SkPoint& t0, const SkPoint& t1,
151                             const SkPoint& t2, SkScalar bary[3]) {
152     // Check sign of t0 to (t1,t2). If it is positive, that means the normals point into the
153     // triangle otherwise the normals point outside the triangle so update edge distances as
154     // necessary
155     bool flip = signed_distance(t0, t1, t2) < 0.f;
156 
157     SkScalar d0 = (flip ? -1 : 1) * signed_distance(point, t0, t1);
158     SkScalar d1 = (flip ? -1 : 1) * signed_distance(point, t1, t2);
159     SkScalar d2 = (flip ? -1 : 1) * signed_distance(point, t2, t0);
160     // Be a little forgiving
161     if (d0 < -1e-4f || d1 < -1e-4f || d2 < -1e-4f) {
162         return false;
163     }
164 
165     // Inside, so calculate barycentric coords from the sideline distances
166     SkScalar d01 = (t0 - t1).length();
167     SkScalar d12 = (t1 - t2).length();
168     SkScalar d20 = (t2 - t0).length();
169 
170     if (SkScalarNearlyZero(d12) || SkScalarNearlyZero(d20) || SkScalarNearlyZero(d01)) {
171         // Empty degenerate triangle
172         return false;
173     }
174 
175     // Coordinates for a vertex use distances to the opposite edge
176     bary[0] = d1 * d12;
177     bary[1] = d2 * d20;
178     bary[2] = d0 * d01;
179     // And normalize
180     SkScalar sum = bary[0] + bary[1] + bary[2];
181     bary[0] /= sum;
182     bary[1] /= sum;
183     bary[2] /= sum;
184 
185     return true;
186 }
187 
get_framed_coverage(const SkPoint outer[4],const SkScalar outerCoverages[4],const SkPoint inner[4],const SkScalar innerCoverages[4],const SkRect & geomDomain,const SkPoint & point)188 static SkScalar get_framed_coverage(const SkPoint outer[4], const SkScalar outerCoverages[4],
189                                     const SkPoint inner[4], const SkScalar innerCoverages[4],
190                                     const SkRect& geomDomain, const SkPoint& point) {
191     // Triangles are ordered clock wise. Indices >= 4 refer to inner[i - 4]. Otherwise its outer[i].
192     static const int kFrameTris[] = {
193         0, 1, 4,   4, 1, 5,
194         1, 2, 5,   5, 2, 6,
195         2, 3, 6,   6, 3, 7,
196         3, 0, 7,   7, 0, 4,
197         4, 5, 7,   7, 5, 6
198     };
199     static const int kNumTris = 10;
200 
201     SkScalar bary[3];
202     for (int i = 0; i < kNumTris; ++i) {
203         int i0 = kFrameTris[i * 3];
204         int i1 = kFrameTris[i * 3 + 1];
205         int i2 = kFrameTris[i * 3 + 2];
206 
207         SkPoint t0 = i0 >= 4 ? inner[i0 - 4] : outer[i0];
208         SkPoint t1 = i1 >= 4 ? inner[i1 - 4] : outer[i1];
209         SkPoint t2 = i2 >= 4 ? inner[i2 - 4] : outer[i2];
210         if (inside_triangle(point, t0, t1, t2, bary)) {
211             // Calculate coverage by barycentric interpolation of coverages
212             SkScalar c0 = i0 >= 4 ? innerCoverages[i0 - 4] : outerCoverages[i0];
213             SkScalar c1 = i1 >= 4 ? innerCoverages[i1 - 4] : outerCoverages[i1];
214             SkScalar c2 = i2 >= 4 ? innerCoverages[i2 - 4] : outerCoverages[i2];
215 
216             SkScalar coverage = bary[0] * c0 + bary[1] * c1 + bary[2] * c2;
217             if (coverage < 0.5f) {
218                 // Check distances to domain
219                 SkScalar l = SkScalarPin(point.fX - geomDomain.fLeft, 0.f, 1.f);
220                 SkScalar t = SkScalarPin(point.fY - geomDomain.fTop, 0.f, 1.f);
221                 SkScalar r = SkScalarPin(geomDomain.fRight - point.fX, 0.f, 1.f);
222                 SkScalar b = SkScalarPin(geomDomain.fBottom - point.fY, 0.f, 1.f);
223                 coverage = SkMinScalar(coverage, l * t * r * b);
224             }
225             return coverage;
226         }
227     }
228     // Not inside any triangle
229     return 0.f;
230 }
231 
232 static constexpr SkScalar kViewScale = 100.f;
233 static constexpr SkScalar kViewOffset = 200.f;
234 
235 class DegenerateQuadSample : public Sample {
236 public:
DegenerateQuadSample(const SkRect & rect)237     DegenerateQuadSample(const SkRect& rect)
238             : fOuterRect(rect)
239             , fCoverageMode(CoverageMode::kArea) {
240         fOuterRect.toQuad(fCorners);
241         for (int i = 0; i < 4; ++i) {
242             fEdgeAA[i] = true;
243         }
244     }
245 
onDrawContent(SkCanvas * canvas)246     void onDrawContent(SkCanvas* canvas) override {
247         static const SkScalar kDotParams[2] = {1.f / kViewScale, 12.f / kViewScale};
248         sk_sp<SkPathEffect> dots = SkDashPathEffect::Make(kDotParams, 2, 0.f);
249         static const SkScalar kDashParams[2] = {8.f / kViewScale, 12.f / kViewScale};
250         sk_sp<SkPathEffect> dashes = SkDashPathEffect::Make(kDashParams, 2, 0.f);
251 
252         SkPaint circlePaint;
253         circlePaint.setAntiAlias(true);
254 
255         SkPaint linePaint;
256         linePaint.setAntiAlias(true);
257         linePaint.setStyle(SkPaint::kStroke_Style);
258         linePaint.setStrokeWidth(4.f / kViewScale);
259         linePaint.setStrokeJoin(SkPaint::kRound_Join);
260         linePaint.setStrokeCap(SkPaint::kRound_Cap);
261 
262         canvas->translate(kViewOffset, kViewOffset);
263         canvas->scale(kViewScale, kViewScale);
264 
265         // Draw the outer rectangle as a dotted line
266         linePaint.setPathEffect(dots);
267         canvas->drawRect(fOuterRect, linePaint);
268 
269         bool valid = this->isValid();
270 
271         if (valid) {
272             SkPoint outsets[8];
273             SkPoint insets[8];
274             // Calculate inset and outset lines for edge-distance visualization
275             for (int i = 0; i < 4; ++i) {
276                 make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], true, outsets + i * 2);
277                 make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], false, insets + i * 2);
278             }
279 
280             // Calculate inner and outer meshes for GPU visualization
281             SkPoint gpuOutset[4];
282             SkScalar gpuOutsetCoverage[4];
283             SkPoint gpuInset[4];
284             SkScalar gpuInsetCoverage[4];
285             SkRect gpuDomain;
286             this->getTessellatedPoints(gpuInset, gpuInsetCoverage, gpuOutset, gpuOutsetCoverage,
287                                        &gpuDomain);
288 
289             // Visualize the coverage values across the clamping rectangle, but test pixels outside
290             // of the "outer" rect since some quad edges can be outset extra far.
291             SkPaint pixelPaint;
292             pixelPaint.setAntiAlias(true);
293             SkRect covRect = fOuterRect.makeOutset(2.f, 2.f);
294             for (SkScalar py = covRect.fTop; py < covRect.fBottom; py += 1.f) {
295                 for (SkScalar px = covRect.fLeft; px < covRect.fRight; px += 1.f) {
296                     // px and py are the top-left corner of the current pixel, so get center's
297                     // coordinate
298                     SkPoint pixelCenter = {px + 0.5f, py + 0.5f};
299                     SkScalar coverage;
300                     if (fCoverageMode == CoverageMode::kArea) {
301                         coverage = get_area_coverage(fEdgeAA, fCorners, pixelCenter);
302                     } else if (fCoverageMode == CoverageMode::kEdgeDistance) {
303                         coverage = get_edge_dist_coverage(fEdgeAA, fCorners, outsets, insets,
304                                                           pixelCenter);
305                     } else {
306                         SkASSERT(fCoverageMode == CoverageMode::kGPUMesh);
307                         coverage = get_framed_coverage(gpuOutset, gpuOutsetCoverage,
308                                                        gpuInset, gpuInsetCoverage, gpuDomain,
309                                                        pixelCenter);
310                     }
311 
312                     SkRect pixelRect = SkRect::MakeXYWH(px, py, 1.f, 1.f);
313                     pixelRect.inset(0.1f, 0.1f);
314 
315                     SkScalar a = 1.f - 0.5f * coverage;
316                     pixelPaint.setColor4f({a, a, a, 1.f}, nullptr);
317                     canvas->drawRect(pixelRect, pixelPaint);
318 
319                     pixelPaint.setColor(coverage > 0.f ? SK_ColorGREEN : SK_ColorRED);
320                     pixelRect.inset(0.38f, 0.38f);
321                     canvas->drawRect(pixelRect, pixelPaint);
322                 }
323             }
324 
325             linePaint.setPathEffect(dashes);
326             // Draw the inset/outset "infinite" lines
327             if (fCoverageMode == CoverageMode::kEdgeDistance) {
328                 for (int i = 0; i < 4; ++i) {
329                     if (fEdgeAA[i]) {
330                         linePaint.setColor(SK_ColorBLUE);
331                         draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
332                         linePaint.setColor(SK_ColorGREEN);
333                         draw_extended_line(canvas, linePaint, insets[i * 2], insets[i * 2 + 1]);
334                     } else {
335                         // Both outset and inset are the same line, so only draw one in cyan
336                         linePaint.setColor(SK_ColorCYAN);
337                         draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
338                     }
339                 }
340             }
341 
342             linePaint.setPathEffect(nullptr);
343             // What is tessellated using GrQuadPerEdgeAA
344             if (fCoverageMode == CoverageMode::kGPUMesh) {
345                 SkPath outsetPath;
346                 outsetPath.addPoly(gpuOutset, 4, true);
347                 linePaint.setColor(SK_ColorBLUE);
348                 canvas->drawPath(outsetPath, linePaint);
349 
350                 SkPath insetPath;
351                 insetPath.addPoly(gpuInset, 4, true);
352                 linePaint.setColor(SK_ColorGREEN);
353                 canvas->drawPath(insetPath, linePaint);
354 
355                 SkPaint domainPaint = linePaint;
356                 domainPaint.setStrokeWidth(2.f / kViewScale);
357                 domainPaint.setPathEffect(dashes);
358                 domainPaint.setColor(SK_ColorMAGENTA);
359                 canvas->drawRect(gpuDomain, domainPaint);
360             }
361 
362             // Draw the edges of the true quad as a solid line
363             SkPath path;
364             path.addPoly(fCorners, 4, true);
365             linePaint.setColor(SK_ColorBLACK);
366             canvas->drawPath(path, linePaint);
367         } else {
368             // Draw the edges of the true quad as a solid *red* line
369             SkPath path;
370             path.addPoly(fCorners, 4, true);
371             linePaint.setColor(SK_ColorRED);
372             linePaint.setPathEffect(nullptr);
373             canvas->drawPath(path, linePaint);
374         }
375 
376         // Draw the four clickable corners as circles
377         circlePaint.setColor(valid ? SK_ColorBLACK : SK_ColorRED);
378         for (int i = 0; i < 4; ++i) {
379             canvas->drawCircle(fCorners[i], 5.f / kViewScale, circlePaint);
380         }
381     }
382 
383     Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, ModifierKey) override;
384     bool onClick(Sample::Click*) override;
385     bool onChar(SkUnichar) override;
name()386     SkString name() override { return SkString("DegenerateQuad"); }
387 
388 private:
389     class Click;
390 
391     enum class CoverageMode {
392         kArea, kEdgeDistance, kGPUMesh
393     };
394 
395     const SkRect fOuterRect;
396     SkPoint fCorners[4]; // TL, TR, BR, BL
397     bool fEdgeAA[4]; // T, R, B, L
398     CoverageMode fCoverageMode;
399 
isValid() const400     bool isValid() const {
401         SkPath path;
402         path.addPoly(fCorners, 4, true);
403         return path.isConvex();
404     }
405 
getTessellatedPoints(SkPoint inset[4],SkScalar insetCoverage[4],SkPoint outset[4],SkScalar outsetCoverage[4],SkRect * domain) const406     void getTessellatedPoints(SkPoint inset[4], SkScalar insetCoverage[4], SkPoint outset[4],
407                               SkScalar outsetCoverage[4], SkRect* domain) const {
408         // Fixed vertex spec for extracting the picture frame geometry
409         static const GrQuadPerEdgeAA::VertexSpec kSpec =
410             {GrQuad::Type::kGeneral, GrQuadPerEdgeAA::ColorType::kNone,
411              GrQuad::Type::kAxisAligned, false, GrQuadPerEdgeAA::Domain::kNo,
412              GrAAType::kCoverage, false};
413         static const GrQuad kIgnored(SkRect::MakeEmpty());
414 
415         GrQuadAAFlags flags = GrQuadAAFlags::kNone;
416         flags |= fEdgeAA[0] ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone;
417         flags |= fEdgeAA[1] ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone;
418         flags |= fEdgeAA[2] ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone;
419         flags |= fEdgeAA[3] ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone;
420 
421         GrQuad quad = GrQuad::MakeFromSkQuad(fCorners, SkMatrix::I());
422 
423         float vertices[56]; // 2 quads, with x, y, coverage, and geometry domain (7 floats x 8 vert)
424         GrQuadPerEdgeAA::Tessellate(vertices, kSpec, quad, {1.f, 1.f, 1.f, 1.f},
425                 GrQuad(SkRect::MakeEmpty()), SkRect::MakeEmpty(), flags);
426 
427         // The first quad in vertices is the inset, then the outset, but they
428         // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange
429         inset[0] = {vertices[0], vertices[1]}; // TL
430         insetCoverage[0] = vertices[2];
431         inset[3] = {vertices[7], vertices[8]}; // BL
432         insetCoverage[3] = vertices[9];
433         inset[1] = {vertices[14], vertices[15]}; // TR
434         insetCoverage[1] = vertices[16];
435         inset[2] = {vertices[21], vertices[22]}; // BR
436         insetCoverage[2] = vertices[23];
437 
438         outset[0] = {vertices[28], vertices[29]}; // TL
439         outsetCoverage[0] = vertices[30];
440         outset[3] = {vertices[35], vertices[36]}; // BL
441         outsetCoverage[3] = vertices[37];
442         outset[1] = {vertices[42], vertices[43]}; // TR
443         outsetCoverage[1] = vertices[44];
444         outset[2] = {vertices[49], vertices[50]}; // BR
445         outsetCoverage[2] = vertices[51];
446 
447         *domain = {vertices[52], vertices[53], vertices[54], vertices[55]};
448     }
449 
450     typedef Sample INHERITED;
451 };
452 
453 class DegenerateQuadSample::Click : public Sample::Click {
454 public:
Click(const SkRect & clamp,int index)455     Click(const SkRect& clamp, int index)
456             : fOuterRect(clamp)
457             , fIndex(index) {}
458 
doClick(SkPoint points[4])459     void doClick(SkPoint points[4]) {
460         if (fIndex >= 0) {
461             this->drag(&points[fIndex]);
462         } else {
463             for (int i = 0; i < 4; ++i) {
464                 this->drag(&points[i]);
465             }
466         }
467     }
468 
469 private:
470     SkRect fOuterRect;
471     int fIndex;
472 
drag(SkPoint * point)473     void drag(SkPoint* point) {
474         SkPoint delta = fCurr - fPrev;
475         *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale);
476         point->fX = SkMinScalar(fOuterRect.fRight, SkMaxScalar(point->fX, fOuterRect.fLeft));
477         point->fY = SkMinScalar(fOuterRect.fBottom, SkMaxScalar(point->fY, fOuterRect.fTop));
478     }
479 };
480 
onFindClickHandler(SkScalar x,SkScalar y,ModifierKey)481 Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, ModifierKey) {
482     SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale);
483     for (int i = 0; i < 4; ++i) {
484         if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) {
485             return new Click(fOuterRect, i);
486         }
487     }
488     return new Click(fOuterRect, -1);
489 }
490 
onClick(Sample::Click * click)491 bool DegenerateQuadSample::onClick(Sample::Click* click) {
492     Click* myClick = (Click*) click;
493     myClick->doClick(fCorners);
494     return true;
495 }
496 
onChar(SkUnichar code)497 bool DegenerateQuadSample::onChar(SkUnichar code) {
498         switch(code) {
499             case '1':
500                 fEdgeAA[0] = !fEdgeAA[0];
501                 return true;
502             case '2':
503                 fEdgeAA[1] = !fEdgeAA[1];
504                 return true;
505             case '3':
506                 fEdgeAA[2] = !fEdgeAA[2];
507                 return true;
508             case '4':
509                 fEdgeAA[3] = !fEdgeAA[3];
510                 return true;
511             case 'q':
512                 fCoverageMode = CoverageMode::kArea;
513                 return true;
514             case 'w':
515                 fCoverageMode = CoverageMode::kEdgeDistance;
516                 return true;
517             case 'e':
518                 fCoverageMode = CoverageMode::kGPUMesh;
519                 return true;
520         }
521         return false;
522 }
523 
524 DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));)
525