• 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 = SkTPin(point.fX - geomDomain.fLeft, 0.f, 1.f);
220                 SkScalar t = SkTPin(point.fY - geomDomain.fTop, 0.f, 1.f);
221                 SkScalar r = SkTPin(geomDomain.fRight - point.fX, 0.f, 1.f);
222                 SkScalar b = SkTPin(geomDomain.fBottom - point.fY, 0.f, 1.f);
223                 coverage = std::min(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, skui::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, GrQuadPerEdgeAA::IndexBufferOption::kPictureFramed};
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::Tessellator tessellator(kSpec, (char*) vertices);
425         tessellator.append(&quad, nullptr, {1.f, 1.f, 1.f, 1.f},
426                            SkRect::MakeEmpty(), flags);
427 
428         // The first quad in vertices is the inset, then the outset, but they
429         // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange
430         inset[0] = {vertices[0], vertices[1]}; // TL
431         insetCoverage[0] = vertices[2];
432         inset[3] = {vertices[7], vertices[8]}; // BL
433         insetCoverage[3] = vertices[9];
434         inset[1] = {vertices[14], vertices[15]}; // TR
435         insetCoverage[1] = vertices[16];
436         inset[2] = {vertices[21], vertices[22]}; // BR
437         insetCoverage[2] = vertices[23];
438 
439         outset[0] = {vertices[28], vertices[29]}; // TL
440         outsetCoverage[0] = vertices[30];
441         outset[3] = {vertices[35], vertices[36]}; // BL
442         outsetCoverage[3] = vertices[37];
443         outset[1] = {vertices[42], vertices[43]}; // TR
444         outsetCoverage[1] = vertices[44];
445         outset[2] = {vertices[49], vertices[50]}; // BR
446         outsetCoverage[2] = vertices[51];
447 
448         *domain = {vertices[52], vertices[53], vertices[54], vertices[55]};
449     }
450 
451     typedef Sample INHERITED;
452 };
453 
454 class DegenerateQuadSample::Click : public Sample::Click {
455 public:
Click(const SkRect & clamp,int index)456     Click(const SkRect& clamp, int index)
457             : fOuterRect(clamp)
458             , fIndex(index) {}
459 
doClick(SkPoint points[4])460     void doClick(SkPoint points[4]) {
461         if (fIndex >= 0) {
462             this->drag(&points[fIndex]);
463         } else {
464             for (int i = 0; i < 4; ++i) {
465                 this->drag(&points[i]);
466             }
467         }
468     }
469 
470 private:
471     SkRect fOuterRect;
472     int fIndex;
473 
drag(SkPoint * point)474     void drag(SkPoint* point) {
475         SkPoint delta = fCurr - fPrev;
476         *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale);
477         point->fX = std::min(fOuterRect.fRight, std::max(point->fX, fOuterRect.fLeft));
478         point->fY = std::min(fOuterRect.fBottom, std::max(point->fY, fOuterRect.fTop));
479     }
480 };
481 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey)482 Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) {
483     SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale);
484     for (int i = 0; i < 4; ++i) {
485         if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) {
486             return new Click(fOuterRect, i);
487         }
488     }
489     return new Click(fOuterRect, -1);
490 }
491 
onClick(Sample::Click * click)492 bool DegenerateQuadSample::onClick(Sample::Click* click) {
493     Click* myClick = (Click*) click;
494     myClick->doClick(fCorners);
495     return true;
496 }
497 
onChar(SkUnichar code)498 bool DegenerateQuadSample::onChar(SkUnichar code) {
499         switch(code) {
500             case '1':
501                 fEdgeAA[0] = !fEdgeAA[0];
502                 return true;
503             case '2':
504                 fEdgeAA[1] = !fEdgeAA[1];
505                 return true;
506             case '3':
507                 fEdgeAA[2] = !fEdgeAA[2];
508                 return true;
509             case '4':
510                 fEdgeAA[3] = !fEdgeAA[3];
511                 return true;
512             case 'q':
513                 fCoverageMode = CoverageMode::kArea;
514                 return true;
515             case 'w':
516                 fCoverageMode = CoverageMode::kEdgeDistance;
517                 return true;
518             case 'e':
519                 fCoverageMode = CoverageMode::kGPUMesh;
520                 return true;
521         }
522         return false;
523 }
524 
525 DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));)
526