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 = 5;
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}, {1, std::numeric_limits<float>::quiet_NaN()}}, 3,
68 CellFillMode::kStretch}, // Flat QUAD with a cusp
69 {{{1,1}, {100,1}, {25,1}, {.3f, std::numeric_limits<float>::quiet_NaN()}}, 3,
70 CellFillMode::kStretch}, // Flat CONIC with a cusp
71 {{{1,1}, {100,1}, {25,1}, {1.5f, std::numeric_limits<float>::quiet_NaN()}}, 3,
72 CellFillMode::kStretch}, // Flat CONIC with a cusp
73 };
74
calc_tight_cubic_bounds(const SkPoint P[4],int depth=5)75 static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
76 if (0 == depth) {
77 SkRect bounds;
78 bounds.fLeft = std::min(std::min(P[0].x(), P[1].x()), std::min(P[2].x(), P[3].x()));
79 bounds.fTop = std::min(std::min(P[0].y(), P[1].y()), std::min(P[2].y(), P[3].y()));
80 bounds.fRight = std::max(std::max(P[0].x(), P[1].x()), std::max(P[2].x(), P[3].x()));
81 bounds.fBottom = std::max(std::max(P[0].y(), P[1].y()), std::max(P[2].y(), P[3].y()));
82 return bounds;
83 }
84
85 SkPoint chopped[7];
86 SkChopCubicAt(P, chopped, .5f);
87 SkRect bounds = calc_tight_cubic_bounds(chopped, depth - 1);
88 bounds.join(calc_tight_cubic_bounds(chopped+3, depth - 1));
89 return bounds;
90 }
91
lerp(const SkPoint & a,const SkPoint & b,float T)92 static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
93 SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
94 return (b - a) * T + a;
95 }
96
97 enum class FillMode {
98 kCenter,
99 kScale
100 };
101
draw_test(SkCanvas * canvas,SkPaint::Cap cap,SkPaint::Join join)102 static void draw_test(SkCanvas* canvas, SkPaint::Cap cap, SkPaint::Join join) {
103 SkRandom rand;
104
105 if (canvas->recordingContext() &&
106 canvas->recordingContext()->priv().caps()->shaderCaps()->tessellationSupport() &&
107 canvas->recordingContext()->priv().caps()->shaderCaps()->maxTessellationSegments() < 64) {
108 // There are fewer tessellation segments than the spec minimum. It must have been overriden
109 // for testing. Indicate this in the background color.
110 canvas->clear(SkColorSetARGB(255, 64, 0, 0));
111 } else {
112 canvas->clear(SK_ColorBLACK);
113 }
114
115 SkPaint strokePaint;
116 strokePaint.setAntiAlias(true);
117 strokePaint.setStrokeWidth(kStrokeWidth);
118 strokePaint.setStyle(SkPaint::kStroke_Style);
119 strokePaint.setStrokeCap(cap);
120 strokePaint.setStrokeJoin(join);
121
122 for (size_t i = 0; i < SK_ARRAY_COUNT(kTrickyCubics); ++i) {
123 auto [originalPts, numPts, fillMode, scale] = kTrickyCubics[i];
124
125 SkASSERT(numPts <= 4);
126 SkPoint p[4];
127 memcpy(p, originalPts, sizeof(SkPoint) * numPts);
128 for (int j = 0; j < numPts; ++j) {
129 p[j] *= scale;
130 }
131 float w = originalPts[3].fX;
132
133 auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
134 kCellSize, kCellSize);
135
136 SkRect strokeBounds;
137 if (numPts == 4) {
138 strokeBounds = calc_tight_cubic_bounds(p);
139 } else {
140 SkASSERT(numPts == 3);
141 SkPoint asCubic[4] = {p[0], lerp(p[0], p[1], 2/3.f), lerp(p[1], p[2], 1/3.f), p[2]};
142 strokeBounds = calc_tight_cubic_bounds(asCubic);
143 }
144 strokeBounds.outset(kStrokeWidth, kStrokeWidth);
145
146 SkMatrix matrix;
147 if (fillMode == CellFillMode::kStretch) {
148 matrix = SkMatrix::RectToRect(strokeBounds, cellRect, SkMatrix::kCenter_ScaleToFit);
149 } else {
150 matrix.setTranslate(cellRect.x() + kStrokeWidth +
151 (cellRect.width() - strokeBounds.width()) / 2,
152 cellRect.y() + kStrokeWidth +
153 (cellRect.height() - strokeBounds.height()) / 2);
154 }
155
156 SkAutoCanvasRestore acr(canvas, true);
157 canvas->concat(matrix);
158 strokePaint.setStrokeWidth(kStrokeWidth / matrix.getMaxScale());
159 strokePaint.setColor(rand.nextU() | 0xff808080);
160 SkPath path = SkPath().moveTo(p[0]);
161 if (numPts == 4) {
162 path.cubicTo(p[1], p[2], p[3]);
163 } else if (w == 1) {
164 SkASSERT(numPts == 3);
165 path.quadTo(p[1], p[2]);
166 } else {
167 SkASSERT(numPts == 3);
168 path.conicTo(p[1], p[2], w);
169 }
170 canvas->drawPath(path, strokePaint);
171 }
172 }
173
DEF_SIMPLE_GM(trickycubicstrokes,canvas,kTestWidth,kTestHeight)174 DEF_SIMPLE_GM(trickycubicstrokes, canvas, kTestWidth, kTestHeight) {
175 draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
176 }
177
DEF_SIMPLE_GM(trickycubicstrokes_roundcaps,canvas,kTestWidth,kTestHeight)178 DEF_SIMPLE_GM(trickycubicstrokes_roundcaps, canvas, kTestWidth, kTestHeight) {
179 draw_test(canvas, SkPaint::kRound_Cap, SkPaint::kRound_Join);
180 }
181
182 class TrickyCubicStrokes_tess_segs_5 : public skiagm::GpuGM {
onShortName()183 SkString onShortName() override {
184 return SkString("trickycubicstrokes_tess_segs_5");
185 }
186
onISize()187 SkISize onISize() override {
188 return SkISize::Make(kTestWidth, kTestHeight);
189 }
190
191 // Pick a very small, odd (and better yet, prime) number of segments.
192 //
193 // - Odd because it makes the tessellation strip asymmetric, which will be important to test for
194 // future plans that involve drawing in reverse order.
195 //
196 // - >=4 because the tessellator code will just assume we have enough to combine a miter join
197 // and line in a single patch. (Requires 4 segments. Spec required minimum is 64.)
198 static constexpr int kMaxTessellationSegmentsOverride = 5;
199
modifyGrContextOptions(GrContextOptions * options)200 void modifyGrContextOptions(GrContextOptions* options) override {
201 options->fMaxTessellationSegmentsOverride = kMaxTessellationSegmentsOverride;
202 // Only allow the tessellation path renderer.
203 options->fGpuPathRenderers = (GpuPathRenderers)((int)options->fGpuPathRenderers &
204 (int)GpuPathRenderers::kTessellation);
205 }
206
onDraw(GrRecordingContext * context,GrSurfaceDrawContext *,SkCanvas * canvas,SkString * errorMsg)207 DrawResult onDraw(GrRecordingContext* context, GrSurfaceDrawContext*, SkCanvas* canvas,
208 SkString* errorMsg) override {
209 if (!context->priv().caps()->shaderCaps()->tessellationSupport() ||
210 !GrTessellationPathRenderer::IsSupported(*context->priv().caps())) {
211 errorMsg->set("Tessellation not supported.");
212 return DrawResult::kSkip;
213 }
214 auto opts = context->priv().drawingManager()->testingOnly_getOptionsForPathRendererChain();
215 if (!(opts.fGpuPathRenderers & GpuPathRenderers::kTessellation)) {
216 errorMsg->set("GrTessellationPathRenderer disabled.");
217 return DrawResult::kSkip;
218 }
219 if (context->priv().caps()->shaderCaps()->maxTessellationSegments() !=
220 kMaxTessellationSegmentsOverride) {
221 errorMsg->set("modifyGrContextOptions did not affect maxTessellationSegments. "
222 "(Are you running viewer? If so use '--maxTessellationSegments 5'.)");
223 return DrawResult::kFail;
224 }
225 // Suppress a tessellator warning message that caps.maxTessellationSegments is too small.
226 GrRecordingContextPriv::AutoSuppressWarningMessages aswm(context);
227 draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
228 return DrawResult::kOk;
229 }
230 };
231
232 DEF_GM( return new TrickyCubicStrokes_tess_segs_5; )
233