blob: 059edea84cb4260366fb8e0b45124cbd3fc61d5b [file] [log] [blame]
Chris Dalton09a7bb22018-08-31 19:53:15 +08001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "gm/gm.h"
9#include "include/core/SkCanvas.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040010#include "include/core/SkColor.h"
11#include "include/core/SkMatrix.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050012#include "include/core/SkPaint.h"
13#include "include/core/SkPath.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040014#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"
Chris Dalton31634282020-09-17 12:16:54 -060019#include "include/gpu/GrContextOptions.h"
Robert Phillipsedcd4312021-06-03 10:14:16 -040020#include "include/gpu/GrDirectContext.h"
Robert Phillips3674f582021-06-16 12:05:54 -040021#include "include/utils/SkRandom.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050022#include "src/core/SkGeometry.h"
Robert Phillipscc44feb2021-07-06 12:21:37 -040023#include "src/gpu/GrCaps.h"
Robert Phillipsedcd4312021-06-03 10:14:16 -040024#include "src/gpu/GrDirectContextPriv.h"
Chris Dalton31634282020-09-17 12:16:54 -060025#include "src/gpu/GrDrawingManager.h"
Robert Phillips3674f582021-06-16 12:05:54 -040026#include "src/gpu/GrRecordingContextPriv.h"
Chris Dalton09a7bb22018-08-31 19:53:15 +080027
Chris Dalton71e21262020-08-20 09:42:12 -060028static constexpr float kStrokeWidth = 30;
Chris Dalton09a7bb22018-08-31 19:53:15 +080029static constexpr int kCellSize = 200;
Chris Daltonc2ae19c2020-09-09 08:35:13 -060030static constexpr int kNumCols = 5;
Chris Dalton7b807262020-12-10 10:22:50 -070031static constexpr int kNumRows = 5;
Chris Dalton31634282020-09-17 12:16:54 -060032static constexpr int kTestWidth = kNumCols * kCellSize;
33static constexpr int kTestHeight = kNumRows * kCellSize;
Chris Dalton09a7bb22018-08-31 19:53:15 +080034
Chris Dalton71e21262020-08-20 09:42:12 -060035enum class CellFillMode {
36 kStretch,
37 kCenter
38};
39
40struct TrickyCubic {
41 SkPoint fPoints[4];
Chris Dalton5c3e1a92020-09-15 20:18:21 -060042 int fNumPts;
Chris Dalton71e21262020-08-20 09:42:12 -060043 CellFillMode fFillMode;
44 float fScale = 1;
45};
46
Chris Dalton31634282020-09-17 12:16:54 -060047// This is a compilation of cubics that have given strokers grief. Feel free to add more.
Chris Dalton71e21262020-08-20 09:42:12 -060048static const TrickyCubic kTrickyCubics[] = {
Chris Dalton5c3e1a92020-09-15 20:18:21 -060049 {{{122, 737}, {348, 553}, {403, 761}, {400, 760}}, 4, CellFillMode::kStretch},
50 {{{244, 520}, {244, 518}, {1141, 634}, {394, 688}}, 4, CellFillMode::kStretch},
51 {{{550, 194}, {138, 130}, {1035, 246}, {288, 300}}, 4, CellFillMode::kStretch},
52 {{{226, 733}, {556, 779}, {-43, 471}, {348, 683}}, 4, CellFillMode::kStretch},
53 {{{268, 204}, {492, 304}, {352, 23}, {433, 412}}, 4, CellFillMode::kStretch},
54 {{{172, 480}, {396, 580}, {256, 299}, {338, 677}}, 4, CellFillMode::kStretch},
55 {{{731, 340}, {318, 252}, {1026, -64}, {367, 265}}, 4, CellFillMode::kStretch},
56 {{{475, 708}, {62, 620}, {770, 304}, {220, 659}}, 4, CellFillMode::kStretch},
57 {{{0, 0}, {128, 128}, {128, 0}, {0, 128}}, 4, CellFillMode::kCenter}, // Perfect cusp
58 {{{0,.01f}, {128,127.999f}, {128,.01f}, {0,127.99f}}, 4, CellFillMode::kCenter}, // Near-cusp
59 {{{0,-.01f}, {128,128.001f}, {128,-.01f}, {0,128.001f}}, 4, CellFillMode::kCenter}, // Near-cusp
60 {{{0,0}, {0,-10}, {0,-10}, {0,10}}, 4, CellFillMode::kCenter, 1.098283f}, // Flat line with 180
61 {{{10,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch}, // Flat line with 2 180s
62 {{{39,-39}, {40,-40}, {40,-40}, {0,0}}, 4, CellFillMode::kStretch}, // Flat diagonal with 180
63 {{{40, 40}, {0, 0}, {200, 200}, {0, 0}}, 4, CellFillMode::kStretch}, // Diag w/ an internal 180
64 {{{0,0}, {1e-2f,0}, {-1e-2f,0}, {0,0}}, 4, CellFillMode::kCenter}, // Circle
65 {{{400.75f,100.05f}, {400.75f,100.05f}, {100.05f,300.95f}, {100.05f,300.95f}}, 4,
Chris Daltonc2ae19c2020-09-09 08:35:13 -060066 CellFillMode::kStretch}, // Flat line with no turns
Chris Dalton5c3e1a92020-09-15 20:18:21 -060067 {{{0.5f,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch}, // Flat line with 2 180s
68 {{{10,0}, {0,0}, {10,0}, {10,0}}, 4, CellFillMode::kStretch}, // Flat line with a 180
Chris Dalton7b807262020-12-10 10:22:50 -070069 {{{1,1}, {2,1}, {1,1}, {1, std::numeric_limits<float>::quiet_NaN()}}, 3,
70 CellFillMode::kStretch}, // Flat QUAD with a cusp
71 {{{1,1}, {100,1}, {25,1}, {.3f, std::numeric_limits<float>::quiet_NaN()}}, 3,
72 CellFillMode::kStretch}, // Flat CONIC with a cusp
73 {{{1,1}, {100,1}, {25,1}, {1.5f, std::numeric_limits<float>::quiet_NaN()}}, 3,
74 CellFillMode::kStretch}, // Flat CONIC with a cusp
Chris Dalton09a7bb22018-08-31 19:53:15 +080075};
76
77static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
78 if (0 == depth) {
79 SkRect bounds;
Brian Osman788b9162020-02-07 10:36:46 -050080 bounds.fLeft = std::min(std::min(P[0].x(), P[1].x()), std::min(P[2].x(), P[3].x()));
81 bounds.fTop = std::min(std::min(P[0].y(), P[1].y()), std::min(P[2].y(), P[3].y()));
82 bounds.fRight = std::max(std::max(P[0].x(), P[1].x()), std::max(P[2].x(), P[3].x()));
83 bounds.fBottom = std::max(std::max(P[0].y(), P[1].y()), std::max(P[2].y(), P[3].y()));
Chris Dalton09a7bb22018-08-31 19:53:15 +080084 return bounds;
85 }
86
87 SkPoint chopped[7];
88 SkChopCubicAt(P, chopped, .5f);
89 SkRect bounds = calc_tight_cubic_bounds(chopped, depth - 1);
90 bounds.join(calc_tight_cubic_bounds(chopped+3, depth - 1));
91 return bounds;
92}
93
Chris Dalton5c3e1a92020-09-15 20:18:21 -060094static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
95 SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
96 return (b - a) * T + a;
97}
98
Chris Dalton71e21262020-08-20 09:42:12 -060099enum class FillMode {
100 kCenter,
101 kScale
102};
103
Chris Dalton7d979912021-05-11 12:36:51 -0600104static void draw_test(SkCanvas* canvas, SkPaint::Cap cap, SkPaint::Join join) {
105 SkRandom rand;
106
107 if (canvas->recordingContext() &&
108 canvas->recordingContext()->priv().caps()->shaderCaps()->tessellationSupport() &&
Chris Daltonb0355a72021-05-25 23:49:39 -0600109 canvas->recordingContext()->priv().caps()->shaderCaps()->maxTessellationSegments() == 5) {
110 // The caller successfully overrode the max tessellation segments to 5. Indicate this in the
111 // background color.
Chris Dalton7d979912021-05-11 12:36:51 -0600112 canvas->clear(SkColorSetARGB(255, 64, 0, 0));
113 } else {
114 canvas->clear(SK_ColorBLACK);
115 }
116
Chris Dalton31634282020-09-17 12:16:54 -0600117 SkPaint strokePaint;
118 strokePaint.setAntiAlias(true);
119 strokePaint.setStrokeWidth(kStrokeWidth);
Chris Dalton31634282020-09-17 12:16:54 -0600120 strokePaint.setStyle(SkPaint::kStroke_Style);
Chris Dalton7d979912021-05-11 12:36:51 -0600121 strokePaint.setStrokeCap(cap);
122 strokePaint.setStrokeJoin(join);
Chris Dalton09a7bb22018-08-31 19:53:15 +0800123
Chris Dalton31634282020-09-17 12:16:54 -0600124 for (size_t i = 0; i < SK_ARRAY_COUNT(kTrickyCubics); ++i) {
125 auto [originalPts, numPts, fillMode, scale] = kTrickyCubics[i];
Chris Dalton09a7bb22018-08-31 19:53:15 +0800126
Chris Dalton31634282020-09-17 12:16:54 -0600127 SkASSERT(numPts <= 4);
128 SkPoint p[4];
129 memcpy(p, originalPts, sizeof(SkPoint) * numPts);
130 for (int j = 0; j < numPts; ++j) {
131 p[j] *= scale;
Chris Dalton09a7bb22018-08-31 19:53:15 +0800132 }
Chris Dalton7b807262020-12-10 10:22:50 -0700133 float w = originalPts[3].fX;
Chris Dalton09a7bb22018-08-31 19:53:15 +0800134
Chris Dalton31634282020-09-17 12:16:54 -0600135 auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
Chris Dalton71e21262020-08-20 09:42:12 -0600136 kCellSize, kCellSize);
137
Chris Dalton5c3e1a92020-09-15 20:18:21 -0600138 SkRect strokeBounds;
139 if (numPts == 4) {
140 strokeBounds = calc_tight_cubic_bounds(p);
141 } else {
142 SkASSERT(numPts == 3);
143 SkPoint asCubic[4] = {p[0], lerp(p[0], p[1], 2/3.f), lerp(p[1], p[2], 1/3.f), p[2]};
144 strokeBounds = calc_tight_cubic_bounds(asCubic);
145 }
Chris Dalton09a7bb22018-08-31 19:53:15 +0800146 strokeBounds.outset(kStrokeWidth, kStrokeWidth);
147
148 SkMatrix matrix;
Chris Dalton71e21262020-08-20 09:42:12 -0600149 if (fillMode == CellFillMode::kStretch) {
Mike Reed2ac6ce82021-01-15 12:26:22 -0500150 matrix = SkMatrix::RectToRect(strokeBounds, cellRect, SkMatrix::kCenter_ScaleToFit);
Chris Dalton71e21262020-08-20 09:42:12 -0600151 } else {
152 matrix.setTranslate(cellRect.x() + kStrokeWidth +
153 (cellRect.width() - strokeBounds.width()) / 2,
154 cellRect.y() + kStrokeWidth +
155 (cellRect.height() - strokeBounds.height()) / 2);
156 }
Chris Dalton09a7bb22018-08-31 19:53:15 +0800157
158 SkAutoCanvasRestore acr(canvas, true);
159 canvas->concat(matrix);
Chris Dalton31634282020-09-17 12:16:54 -0600160 strokePaint.setStrokeWidth(kStrokeWidth / matrix.getMaxScale());
Chris Dalton7d979912021-05-11 12:36:51 -0600161 strokePaint.setColor(rand.nextU() | 0xff808080);
Chris Dalton5c3e1a92020-09-15 20:18:21 -0600162 SkPath path = SkPath().moveTo(p[0]);
163 if (numPts == 4) {
164 path.cubicTo(p[1], p[2], p[3]);
Chris Dalton7b807262020-12-10 10:22:50 -0700165 } else if (w == 1) {
Chris Dalton5c3e1a92020-09-15 20:18:21 -0600166 SkASSERT(numPts == 3);
167 path.quadTo(p[1], p[2]);
Chris Dalton7b807262020-12-10 10:22:50 -0700168 } else {
169 SkASSERT(numPts == 3);
170 path.conicTo(p[1], p[2], w);
Chris Dalton5c3e1a92020-09-15 20:18:21 -0600171 }
Chris Dalton31634282020-09-17 12:16:54 -0600172 canvas->drawPath(path, strokePaint);
173 }
174}
175
176DEF_SIMPLE_GM(trickycubicstrokes, canvas, kTestWidth, kTestHeight) {
Chris Dalton7d979912021-05-11 12:36:51 -0600177 draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
178}
179
180DEF_SIMPLE_GM(trickycubicstrokes_roundcaps, canvas, kTestWidth, kTestHeight) {
181 draw_test(canvas, SkPaint::kRound_Cap, SkPaint::kRound_Join);
Chris Dalton31634282020-09-17 12:16:54 -0600182}
183
Robert Phillips7cef6782021-07-01 13:21:37 -0400184#if SK_GPU_V1
Robert Phillips3674f582021-06-16 12:05:54 -0400185#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
186
Robert Phillipsedcd4312021-06-03 10:14:16 -0400187class TrickyCubicStrokes_tess_segs_5 : public skiagm::GM {
Chris Dalton31634282020-09-17 12:16:54 -0600188 SkString onShortName() override {
189 return SkString("trickycubicstrokes_tess_segs_5");
Chris Dalton09a7bb22018-08-31 19:53:15 +0800190 }
191
Chris Dalton31634282020-09-17 12:16:54 -0600192 SkISize onISize() override {
193 return SkISize::Make(kTestWidth, kTestHeight);
194 }
195
Chris Dalton6756afa2020-09-22 16:47:52 -0600196 // Pick a very small, odd (and better yet, prime) number of segments.
197 //
198 // - Odd because it makes the tessellation strip asymmetric, which will be important to test for
199 // future plans that involve drawing in reverse order.
200 //
201 // - >=4 because the tessellator code will just assume we have enough to combine a miter join
202 // and line in a single patch. (Requires 4 segments. Spec required minimum is 64.)
203 static constexpr int kMaxTessellationSegmentsOverride = 5;
204
Chris Dalton31634282020-09-17 12:16:54 -0600205 void modifyGrContextOptions(GrContextOptions* options) override {
Chris Dalton6756afa2020-09-22 16:47:52 -0600206 options->fMaxTessellationSegmentsOverride = kMaxTessellationSegmentsOverride;
Chris Dalton569c01b2021-05-25 10:11:46 -0600207 options->fAlwaysPreferHardwareTessellation = true;
Chris Dalton31634282020-09-17 12:16:54 -0600208 // Only allow the tessellation path renderer.
209 options->fGpuPathRenderers = (GpuPathRenderers)((int)options->fGpuPathRenderers &
210 (int)GpuPathRenderers::kTessellation);
211 }
212
Robert Phillipsedcd4312021-06-03 10:14:16 -0400213 DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
214 auto dContext = GrAsDirectContext(canvas->recordingContext());
215 if (!dContext) {
216 *errorMsg = "GM relies on having access to a live direct context.";
217 return DrawResult::kSkip;
218 }
219
220 if (!dContext->priv().caps()->shaderCaps()->tessellationSupport() ||
221 !GrTessellationPathRenderer::IsSupported(*dContext->priv().caps())) {
Chris Dalton31634282020-09-17 12:16:54 -0600222 errorMsg->set("Tessellation not supported.");
223 return DrawResult::kSkip;
224 }
Robert Phillipsedcd4312021-06-03 10:14:16 -0400225 auto opts = dContext->priv().drawingManager()->testingOnly_getOptionsForPathRendererChain();
Chris Dalton31634282020-09-17 12:16:54 -0600226 if (!(opts.fGpuPathRenderers & GpuPathRenderers::kTessellation)) {
227 errorMsg->set("GrTessellationPathRenderer disabled.");
228 return DrawResult::kSkip;
229 }
Robert Phillipsedcd4312021-06-03 10:14:16 -0400230 if (dContext->priv().caps()->shaderCaps()->maxTessellationSegments() !=
Chris Dalton6756afa2020-09-22 16:47:52 -0600231 kMaxTessellationSegmentsOverride) {
232 errorMsg->set("modifyGrContextOptions did not affect maxTessellationSegments. "
233 "(Are you running viewer? If so use '--maxTessellationSegments 5'.)");
234 return DrawResult::kFail;
235 }
Chris Dalton31634282020-09-17 12:16:54 -0600236 // Suppress a tessellator warning message that caps.maxTessellationSegments is too small.
Robert Phillipsedcd4312021-06-03 10:14:16 -0400237 GrRecordingContextPriv::AutoSuppressWarningMessages aswm(dContext);
Chris Dalton7d979912021-05-11 12:36:51 -0600238 draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
Chris Dalton31634282020-09-17 12:16:54 -0600239 return DrawResult::kOk;
240 }
Chris Dalton09a7bb22018-08-31 19:53:15 +0800241};
242
Chris Dalton31634282020-09-17 12:16:54 -0600243DEF_GM( return new TrickyCubicStrokes_tess_segs_5; )
Robert Phillips7cef6782021-07-01 13:21:37 -0400244#endif // SK_GPU_V1