Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 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 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 8 | #include "gm/gm.h" |
| 9 | #include "include/core/SkCanvas.h" |
Ben Wagner | 7fde8e1 | 2019-05-01 17:28:53 -0400 | [diff] [blame] | 10 | #include "include/core/SkColor.h" |
| 11 | #include "include/core/SkMatrix.h" |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 12 | #include "include/core/SkPaint.h" |
| 13 | #include "include/core/SkPath.h" |
Ben Wagner | 7fde8e1 | 2019-05-01 17:28:53 -0400 | [diff] [blame] | 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" |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 19 | #include "include/gpu/GrContextOptions.h" |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 20 | #include "include/gpu/GrDirectContext.h" |
Robert Phillips | 3674f58 | 2021-06-16 12:05:54 -0400 | [diff] [blame] | 21 | #include "include/utils/SkRandom.h" |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 22 | #include "src/core/SkGeometry.h" |
Robert Phillips | cc44feb | 2021-07-06 12:21:37 -0400 | [diff] [blame] | 23 | #include "src/gpu/GrCaps.h" |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 24 | #include "src/gpu/GrDirectContextPriv.h" |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 25 | #include "src/gpu/GrDrawingManager.h" |
Robert Phillips | 3674f58 | 2021-06-16 12:05:54 -0400 | [diff] [blame] | 26 | #include "src/gpu/GrRecordingContextPriv.h" |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 27 | |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 28 | static constexpr float kStrokeWidth = 30; |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 29 | static constexpr int kCellSize = 200; |
Chris Dalton | c2ae19c | 2020-09-09 08:35:13 -0600 | [diff] [blame] | 30 | static constexpr int kNumCols = 5; |
Chris Dalton | 7b80726 | 2020-12-10 10:22:50 -0700 | [diff] [blame] | 31 | static constexpr int kNumRows = 5; |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 32 | static constexpr int kTestWidth = kNumCols * kCellSize; |
| 33 | static constexpr int kTestHeight = kNumRows * kCellSize; |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 34 | |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 35 | enum class CellFillMode { |
| 36 | kStretch, |
| 37 | kCenter |
| 38 | }; |
| 39 | |
| 40 | struct TrickyCubic { |
| 41 | SkPoint fPoints[4]; |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 42 | int fNumPts; |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 43 | CellFillMode fFillMode; |
| 44 | float fScale = 1; |
| 45 | }; |
| 46 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 47 | // This is a compilation of cubics that have given strokers grief. Feel free to add more. |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 48 | static const TrickyCubic kTrickyCubics[] = { |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 49 | {{{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 Dalton | c2ae19c | 2020-09-09 08:35:13 -0600 | [diff] [blame] | 66 | CellFillMode::kStretch}, // Flat line with no turns |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 67 | {{{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 Dalton | 7b80726 | 2020-12-10 10:22:50 -0700 | [diff] [blame] | 69 | {{{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 Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 75 | }; |
| 76 | |
| 77 | static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) { |
| 78 | if (0 == depth) { |
| 79 | SkRect bounds; |
Brian Osman | 788b916 | 2020-02-07 10:36:46 -0500 | [diff] [blame] | 80 | 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 Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 84 | 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 Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 94 | static 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 Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 99 | enum class FillMode { |
| 100 | kCenter, |
| 101 | kScale |
| 102 | }; |
| 103 | |
Chris Dalton | 7d97991 | 2021-05-11 12:36:51 -0600 | [diff] [blame] | 104 | static 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 Dalton | b0355a7 | 2021-05-25 23:49:39 -0600 | [diff] [blame] | 109 | 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 Dalton | 7d97991 | 2021-05-11 12:36:51 -0600 | [diff] [blame] | 112 | canvas->clear(SkColorSetARGB(255, 64, 0, 0)); |
| 113 | } else { |
| 114 | canvas->clear(SK_ColorBLACK); |
| 115 | } |
| 116 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 117 | SkPaint strokePaint; |
| 118 | strokePaint.setAntiAlias(true); |
| 119 | strokePaint.setStrokeWidth(kStrokeWidth); |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 120 | strokePaint.setStyle(SkPaint::kStroke_Style); |
Chris Dalton | 7d97991 | 2021-05-11 12:36:51 -0600 | [diff] [blame] | 121 | strokePaint.setStrokeCap(cap); |
| 122 | strokePaint.setStrokeJoin(join); |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 123 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 124 | for (size_t i = 0; i < SK_ARRAY_COUNT(kTrickyCubics); ++i) { |
| 125 | auto [originalPts, numPts, fillMode, scale] = kTrickyCubics[i]; |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 126 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 127 | 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 Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 132 | } |
Chris Dalton | 7b80726 | 2020-12-10 10:22:50 -0700 | [diff] [blame] | 133 | float w = originalPts[3].fX; |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 134 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 135 | auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize, |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 136 | kCellSize, kCellSize); |
| 137 | |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 138 | 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 Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 146 | strokeBounds.outset(kStrokeWidth, kStrokeWidth); |
| 147 | |
| 148 | SkMatrix matrix; |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 149 | if (fillMode == CellFillMode::kStretch) { |
Mike Reed | 2ac6ce8 | 2021-01-15 12:26:22 -0500 | [diff] [blame] | 150 | matrix = SkMatrix::RectToRect(strokeBounds, cellRect, SkMatrix::kCenter_ScaleToFit); |
Chris Dalton | 71e2126 | 2020-08-20 09:42:12 -0600 | [diff] [blame] | 151 | } 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 Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 157 | |
| 158 | SkAutoCanvasRestore acr(canvas, true); |
| 159 | canvas->concat(matrix); |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 160 | strokePaint.setStrokeWidth(kStrokeWidth / matrix.getMaxScale()); |
Chris Dalton | 7d97991 | 2021-05-11 12:36:51 -0600 | [diff] [blame] | 161 | strokePaint.setColor(rand.nextU() | 0xff808080); |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 162 | SkPath path = SkPath().moveTo(p[0]); |
| 163 | if (numPts == 4) { |
| 164 | path.cubicTo(p[1], p[2], p[3]); |
Chris Dalton | 7b80726 | 2020-12-10 10:22:50 -0700 | [diff] [blame] | 165 | } else if (w == 1) { |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 166 | SkASSERT(numPts == 3); |
| 167 | path.quadTo(p[1], p[2]); |
Chris Dalton | 7b80726 | 2020-12-10 10:22:50 -0700 | [diff] [blame] | 168 | } else { |
| 169 | SkASSERT(numPts == 3); |
| 170 | path.conicTo(p[1], p[2], w); |
Chris Dalton | 5c3e1a9 | 2020-09-15 20:18:21 -0600 | [diff] [blame] | 171 | } |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 172 | canvas->drawPath(path, strokePaint); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | DEF_SIMPLE_GM(trickycubicstrokes, canvas, kTestWidth, kTestHeight) { |
Chris Dalton | 7d97991 | 2021-05-11 12:36:51 -0600 | [diff] [blame] | 177 | draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join); |
| 178 | } |
| 179 | |
| 180 | DEF_SIMPLE_GM(trickycubicstrokes_roundcaps, canvas, kTestWidth, kTestHeight) { |
| 181 | draw_test(canvas, SkPaint::kRound_Cap, SkPaint::kRound_Join); |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 182 | } |
| 183 | |
Robert Phillips | 7cef678 | 2021-07-01 13:21:37 -0400 | [diff] [blame] | 184 | #if SK_GPU_V1 |
Robert Phillips | 3674f58 | 2021-06-16 12:05:54 -0400 | [diff] [blame] | 185 | #include "src/gpu/tessellate/GrTessellationPathRenderer.h" |
| 186 | |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 187 | class TrickyCubicStrokes_tess_segs_5 : public skiagm::GM { |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 188 | SkString onShortName() override { |
| 189 | return SkString("trickycubicstrokes_tess_segs_5"); |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 190 | } |
| 191 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 192 | SkISize onISize() override { |
| 193 | return SkISize::Make(kTestWidth, kTestHeight); |
| 194 | } |
| 195 | |
Chris Dalton | 6756afa | 2020-09-22 16:47:52 -0600 | [diff] [blame] | 196 | // 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 Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 205 | void modifyGrContextOptions(GrContextOptions* options) override { |
Chris Dalton | 6756afa | 2020-09-22 16:47:52 -0600 | [diff] [blame] | 206 | options->fMaxTessellationSegmentsOverride = kMaxTessellationSegmentsOverride; |
Chris Dalton | 569c01b | 2021-05-25 10:11:46 -0600 | [diff] [blame] | 207 | options->fAlwaysPreferHardwareTessellation = true; |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 208 | // Only allow the tessellation path renderer. |
| 209 | options->fGpuPathRenderers = (GpuPathRenderers)((int)options->fGpuPathRenderers & |
| 210 | (int)GpuPathRenderers::kTessellation); |
| 211 | } |
| 212 | |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 213 | 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 Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 222 | errorMsg->set("Tessellation not supported."); |
| 223 | return DrawResult::kSkip; |
| 224 | } |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 225 | auto opts = dContext->priv().drawingManager()->testingOnly_getOptionsForPathRendererChain(); |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 226 | if (!(opts.fGpuPathRenderers & GpuPathRenderers::kTessellation)) { |
| 227 | errorMsg->set("GrTessellationPathRenderer disabled."); |
| 228 | return DrawResult::kSkip; |
| 229 | } |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 230 | if (dContext->priv().caps()->shaderCaps()->maxTessellationSegments() != |
Chris Dalton | 6756afa | 2020-09-22 16:47:52 -0600 | [diff] [blame] | 231 | 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 Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 236 | // Suppress a tessellator warning message that caps.maxTessellationSegments is too small. |
Robert Phillips | edcd431 | 2021-06-03 10:14:16 -0400 | [diff] [blame] | 237 | GrRecordingContextPriv::AutoSuppressWarningMessages aswm(dContext); |
Chris Dalton | 7d97991 | 2021-05-11 12:36:51 -0600 | [diff] [blame] | 238 | draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join); |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 239 | return DrawResult::kOk; |
| 240 | } |
Chris Dalton | 09a7bb2 | 2018-08-31 19:53:15 +0800 | [diff] [blame] | 241 | }; |
| 242 | |
Chris Dalton | 3163428 | 2020-09-17 12:16:54 -0600 | [diff] [blame] | 243 | DEF_GM( return new TrickyCubicStrokes_tess_segs_5; ) |
Robert Phillips | 7cef678 | 2021-07-01 13:21:37 -0400 | [diff] [blame] | 244 | #endif // SK_GPU_V1 |