1 /*
2  * Copyright 2018 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 "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkSize.h"
17 #include "include/core/SkString.h"
18 #include "include/core/SkTypes.h"
19 #include "include/gpu/GrContextOptions.h"
20 #include "include/gpu/GrRecordingContext.h"
21 #include "src/core/SkGeometry.h"
22 #include "src/gpu/GrDrawingManager.h"
23 #include "src/gpu/GrRecordingContextPriv.h"
24 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
25 
26 static constexpr float kStrokeWidth = 30;
27 static constexpr int kCellSize = 200;
28 static constexpr int kNumCols = 5;
29 static constexpr int kNumRows = 4;
30 static constexpr int kTestWidth = kNumCols * kCellSize;
31 static constexpr int kTestHeight = kNumRows * kCellSize;
32 
33 enum class CellFillMode {
34     kStretch,
35     kCenter
36 };
37 
38 struct TrickyCubic {
39     SkPoint fPoints[4];
40     int fNumPts;
41     CellFillMode fFillMode;
42     float fScale = 1;
43 };
44 
45 // This is a compilation of cubics that have given strokers grief. Feel free to add more.
46 static const TrickyCubic kTrickyCubics[] = {
47     {{{122, 737}, {348, 553}, {403, 761}, {400, 760}}, 4, CellFillMode::kStretch},
48     {{{244, 520}, {244, 518}, {1141, 634}, {394, 688}}, 4, CellFillMode::kStretch},
49     {{{550, 194}, {138, 130}, {1035, 246}, {288, 300}}, 4, CellFillMode::kStretch},
50     {{{226, 733}, {556, 779}, {-43, 471}, {348, 683}}, 4, CellFillMode::kStretch},
51     {{{268, 204}, {492, 304}, {352, 23}, {433, 412}}, 4, CellFillMode::kStretch},
52     {{{172, 480}, {396, 580}, {256, 299}, {338, 677}}, 4, CellFillMode::kStretch},
53     {{{731, 340}, {318, 252}, {1026, -64}, {367, 265}}, 4, CellFillMode::kStretch},
54     {{{475, 708}, {62, 620}, {770, 304}, {220, 659}}, 4, CellFillMode::kStretch},
55     {{{0, 0}, {128, 128}, {128, 0}, {0, 128}}, 4, CellFillMode::kCenter},  // Perfect cusp
56     {{{0,.01f}, {128,127.999f}, {128,.01f}, {0,127.99f}}, 4, CellFillMode::kCenter},  // Near-cusp
57     {{{0,-.01f}, {128,128.001f}, {128,-.01f}, {0,128.001f}}, 4, CellFillMode::kCenter}, // Near-cusp
58     {{{0,0}, {0,-10}, {0,-10}, {0,10}}, 4, CellFillMode::kCenter, 1.098283f},  // Flat line with 180
59     {{{10,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
60     {{{39,-39}, {40,-40}, {40,-40}, {0,0}}, 4, CellFillMode::kStretch},  // Flat diagonal with 180
61     {{{40, 40}, {0, 0}, {200, 200}, {0, 0}}, 4, CellFillMode::kStretch},  // Diag w/ an internal 180
62     {{{0,0}, {1e-2f,0}, {-1e-2f,0}, {0,0}}, 4, CellFillMode::kCenter},  // Circle
63     {{{400.75f,100.05f}, {400.75f,100.05f}, {100.05f,300.95f}, {100.05f,300.95f}}, 4,
64      CellFillMode::kStretch},  // Flat line with no turns
65     {{{0.5f,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
66     {{{10,0}, {0,0}, {10,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with a 180
67     {{{1,1}, {2,1}, {1,1}, {std::numeric_limits<float>::quiet_NaN(), 0}}, 3,
68      CellFillMode::kStretch},  // Flat QUAD with a 180
69 };
70 
calc_tight_cubic_bounds(const SkPoint P[4],int depth=5)71 static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
72     if (0 == depth) {
73         SkRect bounds;
74         bounds.fLeft = std::min(std::min(P[0].x(), P[1].x()), std::min(P[2].x(), P[3].x()));
75         bounds.fTop = std::min(std::min(P[0].y(), P[1].y()), std::min(P[2].y(), P[3].y()));
76         bounds.fRight = std::max(std::max(P[0].x(), P[1].x()), std::max(P[2].x(), P[3].x()));
77         bounds.fBottom = std::max(std::max(P[0].y(), P[1].y()), std::max(P[2].y(), P[3].y()));
78         return bounds;
79     }
80 
81     SkPoint chopped[7];
82     SkChopCubicAt(P, chopped, .5f);
83     SkRect bounds = calc_tight_cubic_bounds(chopped, depth - 1);
84     bounds.join(calc_tight_cubic_bounds(chopped+3, depth - 1));
85     return bounds;
86 }
87 
lerp(const SkPoint & a,const SkPoint & b,float T)88 static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
89     SkASSERT(1 != T);  // The below does not guarantee lerp(a, b, 1) === b.
90     return (b - a) * T + a;
91 }
92 
93 enum class FillMode {
94     kCenter,
95     kScale
96 };
97 
draw_test(SkCanvas * canvas,const SkColor strokeColor)98 static void draw_test(SkCanvas* canvas, const SkColor strokeColor) {
99     SkPaint strokePaint;
100     strokePaint.setAntiAlias(true);
101     strokePaint.setStrokeWidth(kStrokeWidth);
102     strokePaint.setColor(strokeColor);
103     strokePaint.setStyle(SkPaint::kStroke_Style);
104 
105     canvas->clear(SK_ColorBLACK);
106 
107     for (size_t i = 0; i < SK_ARRAY_COUNT(kTrickyCubics); ++i) {
108         auto [originalPts, numPts, fillMode, scale] = kTrickyCubics[i];
109 
110         SkASSERT(numPts <= 4);
111         SkPoint p[4];
112         memcpy(p, originalPts, sizeof(SkPoint) * numPts);
113         for (int j = 0; j < numPts; ++j) {
114             p[j] *= scale;
115         }
116 
117         auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
118                                          kCellSize, kCellSize);
119 
120         SkRect strokeBounds;
121         if (numPts == 4) {
122             strokeBounds = calc_tight_cubic_bounds(p);
123         } else {
124             SkASSERT(numPts == 3);
125             SkPoint asCubic[4] = {p[0], lerp(p[0], p[1], 2/3.f), lerp(p[1], p[2], 1/3.f), p[2]};
126             strokeBounds = calc_tight_cubic_bounds(asCubic);
127         }
128         strokeBounds.outset(kStrokeWidth, kStrokeWidth);
129 
130         SkMatrix matrix;
131         if (fillMode == CellFillMode::kStretch) {
132             matrix.setRectToRect(strokeBounds, cellRect, SkMatrix::kCenter_ScaleToFit);
133         } else {
134             matrix.setTranslate(cellRect.x() + kStrokeWidth +
135                                 (cellRect.width() - strokeBounds.width()) / 2,
136                                 cellRect.y() + kStrokeWidth +
137                                 (cellRect.height() - strokeBounds.height()) / 2);
138         }
139 
140         SkAutoCanvasRestore acr(canvas, true);
141         canvas->concat(matrix);
142         strokePaint.setStrokeWidth(kStrokeWidth / matrix.getMaxScale());
143         SkPath path = SkPath().moveTo(p[0]);
144         if (numPts == 4) {
145             path.cubicTo(p[1], p[2], p[3]);
146         } else {
147             SkASSERT(numPts == 3);
148             path.quadTo(p[1], p[2]);
149         }
150         canvas->drawPath(path, strokePaint);
151     }
152 }
153 
DEF_SIMPLE_GM(trickycubicstrokes,canvas,kTestWidth,kTestHeight)154 DEF_SIMPLE_GM(trickycubicstrokes, canvas, kTestWidth, kTestHeight) {
155     draw_test(canvas, SK_ColorGREEN);
156 }
157 
158 class TrickyCubicStrokes_tess_segs_5 : public skiagm::GpuGM {
onShortName()159     SkString onShortName() override {
160         return SkString("trickycubicstrokes_tess_segs_5");
161     }
162 
onISize()163     SkISize onISize() override {
164         return SkISize::Make(kTestWidth, kTestHeight);
165     }
166 
167     // Pick a very small, odd (and better yet, prime) number of segments.
168     //
169     // - Odd because it makes the tessellation strip asymmetric, which will be important to test for
170     //   future plans that involve drawing in reverse order.
171     //
172     // - >=4 because the tessellator code will just assume we have enough to combine a miter join
173     //   and line in a single patch. (Requires 4 segments. Spec required minimum is 64.)
174     static constexpr int kMaxTessellationSegmentsOverride = 5;
175 
modifyGrContextOptions(GrContextOptions * options)176     void modifyGrContextOptions(GrContextOptions* options) override {
177         options->fMaxTessellationSegmentsOverride = kMaxTessellationSegmentsOverride;
178         // Only allow the tessellation path renderer.
179         options->fGpuPathRenderers = (GpuPathRenderers)((int)options->fGpuPathRenderers &
180                                                         (int)GpuPathRenderers::kTessellation);
181     }
182 
onDraw(GrRecordingContext * context,GrRenderTargetContext *,SkCanvas * canvas,SkString * errorMsg)183     DrawResult onDraw(GrRecordingContext* context, GrRenderTargetContext*, SkCanvas* canvas,
184                       SkString* errorMsg) override {
185         if (!context->priv().caps()->shaderCaps()->tessellationSupport() ||
186             !GrTessellationPathRenderer::IsSupported(*context->priv().caps())) {
187             errorMsg->set("Tessellation not supported.");
188             return DrawResult::kSkip;
189         }
190         auto opts = context->priv().drawingManager()->testingOnly_getOptionsForPathRendererChain();
191         if (!(opts.fGpuPathRenderers & GpuPathRenderers::kTessellation)) {
192             errorMsg->set("GrTessellationPathRenderer disabled.");
193             return DrawResult::kSkip;
194         }
195         if (context->priv().caps()->shaderCaps()->maxTessellationSegments() !=
196             kMaxTessellationSegmentsOverride) {
197             errorMsg->set("modifyGrContextOptions did not affect maxTessellationSegments. "
198                           "(Are you running viewer? If so use '--maxTessellationSegments 5'.)");
199             return DrawResult::kFail;
200         }
201         // Suppress a tessellator warning message that caps.maxTessellationSegments is too small.
202         GrRecordingContextPriv::AutoSuppressWarningMessages aswm(context);
203         draw_test(canvas, SK_ColorRED);
204         return DrawResult::kOk;
205     }
206 };
207 
208 DEF_GM( return new TrickyCubicStrokes_tess_segs_5; )
209