Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 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 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 8 | #include "samplecode/Sample.h" |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 9 | |
Michael Ludwig | fd4f4df | 2019-05-29 09:51:09 -0400 | [diff] [blame] | 10 | #include "src/gpu/geometry/GrQuad.h" |
Robert Phillips | ef80d7b | 2021-09-14 16:10:56 -0400 | [diff] [blame] | 11 | #include "src/gpu/ops/QuadPerEdgeAA.h" |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 12 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 13 | #include "include/core/SkCanvas.h" |
| 14 | #include "include/core/SkPaint.h" |
| 15 | #include "include/effects/SkDashPathEffect.h" |
| 16 | #include "include/pathops/SkPathOps.h" |
Mike Klein | 8aa0edf | 2020-10-16 11:04:18 -0500 | [diff] [blame] | 17 | #include "include/private/SkTPin.h" |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 18 | |
Robert Phillips | ef80d7b | 2021-09-14 16:10:56 -0400 | [diff] [blame] | 19 | using VertexSpec = skgpu::v1::QuadPerEdgeAA::VertexSpec; |
| 20 | using ColorType = skgpu::v1::QuadPerEdgeAA::ColorType; |
| 21 | using Subset = skgpu::v1::QuadPerEdgeAA::Subset; |
| 22 | using IndexBufferOption = skgpu::v1::QuadPerEdgeAA::IndexBufferOption; |
| 23 | |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 24 | // Draw a line through the two points, outset by a fixed length in screen space |
| 25 | static void draw_extended_line(SkCanvas* canvas, const SkPaint paint, |
| 26 | const SkPoint& p0, const SkPoint& p1) { |
| 27 | SkVector v = p1 - p0; |
| 28 | v.setLength(v.length() + 3.f); |
| 29 | canvas->drawLine(p1 - v, p0 + v, paint); |
| 30 | |
| 31 | // Draw normal vector too |
| 32 | SkPaint normalPaint = paint; |
| 33 | normalPaint.setPathEffect(nullptr); |
| 34 | normalPaint.setStrokeWidth(paint.getStrokeWidth() / 4.f); |
| 35 | |
| 36 | SkVector n = {v.fY, -v.fX}; |
| 37 | n.setLength(.25f); |
| 38 | SkPoint m = (p0 + p1) * 0.5f; |
| 39 | canvas->drawLine(m, m + n, normalPaint); |
| 40 | } |
| 41 | |
| 42 | static void make_aa_line(const SkPoint& p0, const SkPoint& p1, bool aaOn, |
| 43 | bool outset, SkPoint line[2]) { |
| 44 | SkVector n = {0.f, 0.f}; |
| 45 | if (aaOn) { |
| 46 | SkVector v = p1 - p0; |
| 47 | n = outset ? SkVector::Make(v.fY, -v.fX) : SkVector::Make(-v.fY, v.fX); |
| 48 | n.setLength(0.5f); |
| 49 | } |
| 50 | |
| 51 | line[0] = p0 + n; |
| 52 | line[1] = p1 + n; |
| 53 | } |
| 54 | |
| 55 | // To the line through l0-l1, not capped at the end points of the segment |
| 56 | static SkScalar signed_distance(const SkPoint& p, const SkPoint& l0, const SkPoint& l1) { |
| 57 | SkVector v = l1 - l0; |
| 58 | v.normalize(); |
| 59 | SkVector n = {v.fY, -v.fX}; |
| 60 | SkScalar c = -n.dot(l0); |
| 61 | return n.dot(p) + c; |
| 62 | } |
| 63 | |
| 64 | static SkScalar get_area_coverage(const bool edgeAA[4], const SkPoint corners[4], |
| 65 | const SkPoint& point) { |
| 66 | SkPath shape; |
| 67 | shape.addPoly(corners, 4, true); |
| 68 | SkPath pixel; |
| 69 | pixel.addRect(SkRect::MakeXYWH(point.fX - 0.5f, point.fY - 0.5f, 1.f, 1.f)); |
| 70 | |
| 71 | SkPath intersection; |
| 72 | if (!Op(shape, pixel, kIntersect_SkPathOp, &intersection) || intersection.isEmpty()) { |
| 73 | return 0.f; |
| 74 | } |
| 75 | |
| 76 | // Calculate area of the convex polygon |
| 77 | SkScalar area = 0.f; |
| 78 | for (int i = 0; i < intersection.countPoints(); ++i) { |
| 79 | SkPoint p0 = intersection.getPoint(i); |
| 80 | SkPoint p1 = intersection.getPoint((i + 1) % intersection.countPoints()); |
| 81 | SkScalar det = p0.fX * p1.fY - p1.fX * p0.fY; |
| 82 | area += det; |
| 83 | } |
| 84 | |
| 85 | // Scale by 1/2, then take abs value (this area formula is signed based on point winding, but |
| 86 | // since it's convex, just make it positive). |
| 87 | area = SkScalarAbs(0.5f * area); |
| 88 | |
| 89 | // Now account for the edge AA. If the pixel center is outside of a non-AA edge, turn of its |
| 90 | // coverage. If the pixel only intersects non-AA edges, then set coverage to 1. |
| 91 | bool needsNonAA = false; |
| 92 | SkScalar edgeD[4]; |
| 93 | for (int i = 0; i < 4; ++i) { |
| 94 | SkPoint e0 = corners[i]; |
| 95 | SkPoint e1 = corners[(i + 1) % 4]; |
| 96 | edgeD[i] = -signed_distance(point, e0, e1); |
| 97 | if (!edgeAA[i]) { |
| 98 | if (edgeD[i] < -1e-4f) { |
| 99 | return 0.f; // Outside of non-AA line |
| 100 | } |
| 101 | needsNonAA = true; |
| 102 | } |
| 103 | } |
| 104 | // Otherwise inside the shape, so check if any AA edge exerts influence over nonAA |
| 105 | if (needsNonAA) { |
| 106 | for (int i = 0; i < 4; i++) { |
| 107 | if (edgeAA[i] && edgeD[i] < 0.5f) { |
| 108 | needsNonAA = false; |
| 109 | break; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | return needsNonAA ? 1.f : area; |
| 114 | } |
| 115 | |
| 116 | // FIXME take into account max coverage properly, |
| 117 | static SkScalar get_edge_dist_coverage(const bool edgeAA[4], const SkPoint corners[4], |
| 118 | const SkPoint outsetLines[8], const SkPoint insetLines[8], |
| 119 | const SkPoint& point) { |
| 120 | bool flip = false; |
| 121 | // If the quad has been inverted, the original corners will not all be on the negative side of |
| 122 | // every outset line. When that happens, calculate coverage using the "inset" lines and flip |
| 123 | // the signed distance |
| 124 | for (int i = 0; i < 4; ++i) { |
| 125 | for (int j = 0; j < 4; ++j) { |
| 126 | SkScalar d = signed_distance(corners[i], outsetLines[j * 2], outsetLines[j * 2 + 1]); |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 127 | if (d > 1e-4f) { |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 128 | flip = true; |
| 129 | break; |
| 130 | } |
| 131 | } |
| 132 | if (flip) { |
| 133 | break; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | const SkPoint* lines = flip ? insetLines : outsetLines; |
| 138 | |
| 139 | SkScalar minCoverage = 1.f; |
| 140 | for (int i = 0; i < 4; ++i) { |
| 141 | // Multiply by negative 1 so that outside points have negative distances |
| 142 | SkScalar d = (flip ? 1 : -1) * signed_distance(point, lines[i * 2], lines[i * 2 + 1]); |
| 143 | if (!edgeAA[i] && d >= -1e-4f) { |
| 144 | d = 1.f; |
| 145 | } |
| 146 | if (d < minCoverage) { |
| 147 | minCoverage = d; |
| 148 | if (minCoverage < 0.f) { |
| 149 | break; // Outside the shape |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | return minCoverage < 0.f ? 0.f : minCoverage; |
| 154 | } |
| 155 | |
| 156 | static bool inside_triangle(const SkPoint& point, const SkPoint& t0, const SkPoint& t1, |
| 157 | const SkPoint& t2, SkScalar bary[3]) { |
| 158 | // Check sign of t0 to (t1,t2). If it is positive, that means the normals point into the |
| 159 | // triangle otherwise the normals point outside the triangle so update edge distances as |
| 160 | // necessary |
| 161 | bool flip = signed_distance(t0, t1, t2) < 0.f; |
| 162 | |
| 163 | SkScalar d0 = (flip ? -1 : 1) * signed_distance(point, t0, t1); |
| 164 | SkScalar d1 = (flip ? -1 : 1) * signed_distance(point, t1, t2); |
| 165 | SkScalar d2 = (flip ? -1 : 1) * signed_distance(point, t2, t0); |
| 166 | // Be a little forgiving |
| 167 | if (d0 < -1e-4f || d1 < -1e-4f || d2 < -1e-4f) { |
| 168 | return false; |
| 169 | } |
| 170 | |
| 171 | // Inside, so calculate barycentric coords from the sideline distances |
| 172 | SkScalar d01 = (t0 - t1).length(); |
| 173 | SkScalar d12 = (t1 - t2).length(); |
| 174 | SkScalar d20 = (t2 - t0).length(); |
| 175 | |
| 176 | if (SkScalarNearlyZero(d12) || SkScalarNearlyZero(d20) || SkScalarNearlyZero(d01)) { |
| 177 | // Empty degenerate triangle |
| 178 | return false; |
| 179 | } |
| 180 | |
| 181 | // Coordinates for a vertex use distances to the opposite edge |
| 182 | bary[0] = d1 * d12; |
| 183 | bary[1] = d2 * d20; |
| 184 | bary[2] = d0 * d01; |
| 185 | // And normalize |
| 186 | SkScalar sum = bary[0] + bary[1] + bary[2]; |
| 187 | bary[0] /= sum; |
| 188 | bary[1] /= sum; |
| 189 | bary[2] /= sum; |
| 190 | |
| 191 | return true; |
| 192 | } |
| 193 | |
| 194 | static SkScalar get_framed_coverage(const SkPoint outer[4], const SkScalar outerCoverages[4], |
| 195 | const SkPoint inner[4], const SkScalar innerCoverages[4], |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 196 | const SkRect& geomDomain, const SkPoint& point) { |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 197 | // Triangles are ordered clock wise. Indices >= 4 refer to inner[i - 4]. Otherwise its outer[i]. |
| 198 | static const int kFrameTris[] = { |
| 199 | 0, 1, 4, 4, 1, 5, |
| 200 | 1, 2, 5, 5, 2, 6, |
| 201 | 2, 3, 6, 6, 3, 7, |
| 202 | 3, 0, 7, 7, 0, 4, |
| 203 | 4, 5, 7, 7, 5, 6 |
| 204 | }; |
| 205 | static const int kNumTris = 10; |
| 206 | |
| 207 | SkScalar bary[3]; |
| 208 | for (int i = 0; i < kNumTris; ++i) { |
| 209 | int i0 = kFrameTris[i * 3]; |
| 210 | int i1 = kFrameTris[i * 3 + 1]; |
| 211 | int i2 = kFrameTris[i * 3 + 2]; |
| 212 | |
| 213 | SkPoint t0 = i0 >= 4 ? inner[i0 - 4] : outer[i0]; |
| 214 | SkPoint t1 = i1 >= 4 ? inner[i1 - 4] : outer[i1]; |
| 215 | SkPoint t2 = i2 >= 4 ? inner[i2 - 4] : outer[i2]; |
| 216 | if (inside_triangle(point, t0, t1, t2, bary)) { |
| 217 | // Calculate coverage by barycentric interpolation of coverages |
| 218 | SkScalar c0 = i0 >= 4 ? innerCoverages[i0 - 4] : outerCoverages[i0]; |
| 219 | SkScalar c1 = i1 >= 4 ? innerCoverages[i1 - 4] : outerCoverages[i1]; |
| 220 | SkScalar c2 = i2 >= 4 ? innerCoverages[i2 - 4] : outerCoverages[i2]; |
| 221 | |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 222 | SkScalar coverage = bary[0] * c0 + bary[1] * c1 + bary[2] * c2; |
| 223 | if (coverage < 0.5f) { |
| 224 | // Check distances to domain |
Brian Osman | aba642c | 2020-02-06 12:52:25 -0500 | [diff] [blame] | 225 | SkScalar l = SkTPin(point.fX - geomDomain.fLeft, 0.f, 1.f); |
| 226 | SkScalar t = SkTPin(point.fY - geomDomain.fTop, 0.f, 1.f); |
| 227 | SkScalar r = SkTPin(geomDomain.fRight - point.fX, 0.f, 1.f); |
| 228 | SkScalar b = SkTPin(geomDomain.fBottom - point.fY, 0.f, 1.f); |
Brian Osman | 116b33e | 2020-02-05 13:34:09 -0500 | [diff] [blame] | 229 | coverage = std::min(coverage, l * t * r * b); |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 230 | } |
| 231 | return coverage; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 232 | } |
| 233 | } |
| 234 | // Not inside any triangle |
| 235 | return 0.f; |
| 236 | } |
| 237 | |
| 238 | static constexpr SkScalar kViewScale = 100.f; |
| 239 | static constexpr SkScalar kViewOffset = 200.f; |
| 240 | |
| 241 | class DegenerateQuadSample : public Sample { |
| 242 | public: |
| 243 | DegenerateQuadSample(const SkRect& rect) |
| 244 | : fOuterRect(rect) |
| 245 | , fCoverageMode(CoverageMode::kArea) { |
| 246 | fOuterRect.toQuad(fCorners); |
| 247 | for (int i = 0; i < 4; ++i) { |
| 248 | fEdgeAA[i] = true; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | void onDrawContent(SkCanvas* canvas) override { |
| 253 | static const SkScalar kDotParams[2] = {1.f / kViewScale, 12.f / kViewScale}; |
| 254 | sk_sp<SkPathEffect> dots = SkDashPathEffect::Make(kDotParams, 2, 0.f); |
| 255 | static const SkScalar kDashParams[2] = {8.f / kViewScale, 12.f / kViewScale}; |
| 256 | sk_sp<SkPathEffect> dashes = SkDashPathEffect::Make(kDashParams, 2, 0.f); |
| 257 | |
| 258 | SkPaint circlePaint; |
| 259 | circlePaint.setAntiAlias(true); |
| 260 | |
| 261 | SkPaint linePaint; |
| 262 | linePaint.setAntiAlias(true); |
| 263 | linePaint.setStyle(SkPaint::kStroke_Style); |
| 264 | linePaint.setStrokeWidth(4.f / kViewScale); |
| 265 | linePaint.setStrokeJoin(SkPaint::kRound_Join); |
| 266 | linePaint.setStrokeCap(SkPaint::kRound_Cap); |
| 267 | |
| 268 | canvas->translate(kViewOffset, kViewOffset); |
| 269 | canvas->scale(kViewScale, kViewScale); |
| 270 | |
| 271 | // Draw the outer rectangle as a dotted line |
| 272 | linePaint.setPathEffect(dots); |
| 273 | canvas->drawRect(fOuterRect, linePaint); |
| 274 | |
| 275 | bool valid = this->isValid(); |
| 276 | |
| 277 | if (valid) { |
| 278 | SkPoint outsets[8]; |
| 279 | SkPoint insets[8]; |
| 280 | // Calculate inset and outset lines for edge-distance visualization |
| 281 | for (int i = 0; i < 4; ++i) { |
| 282 | make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], true, outsets + i * 2); |
| 283 | make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], false, insets + i * 2); |
| 284 | } |
| 285 | |
| 286 | // Calculate inner and outer meshes for GPU visualization |
| 287 | SkPoint gpuOutset[4]; |
| 288 | SkScalar gpuOutsetCoverage[4]; |
| 289 | SkPoint gpuInset[4]; |
| 290 | SkScalar gpuInsetCoverage[4]; |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 291 | SkRect gpuDomain; |
| 292 | this->getTessellatedPoints(gpuInset, gpuInsetCoverage, gpuOutset, gpuOutsetCoverage, |
| 293 | &gpuDomain); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 294 | |
| 295 | // Visualize the coverage values across the clamping rectangle, but test pixels outside |
| 296 | // of the "outer" rect since some quad edges can be outset extra far. |
| 297 | SkPaint pixelPaint; |
| 298 | pixelPaint.setAntiAlias(true); |
| 299 | SkRect covRect = fOuterRect.makeOutset(2.f, 2.f); |
| 300 | for (SkScalar py = covRect.fTop; py < covRect.fBottom; py += 1.f) { |
| 301 | for (SkScalar px = covRect.fLeft; px < covRect.fRight; px += 1.f) { |
| 302 | // px and py are the top-left corner of the current pixel, so get center's |
| 303 | // coordinate |
| 304 | SkPoint pixelCenter = {px + 0.5f, py + 0.5f}; |
| 305 | SkScalar coverage; |
| 306 | if (fCoverageMode == CoverageMode::kArea) { |
| 307 | coverage = get_area_coverage(fEdgeAA, fCorners, pixelCenter); |
| 308 | } else if (fCoverageMode == CoverageMode::kEdgeDistance) { |
| 309 | coverage = get_edge_dist_coverage(fEdgeAA, fCorners, outsets, insets, |
| 310 | pixelCenter); |
| 311 | } else { |
| 312 | SkASSERT(fCoverageMode == CoverageMode::kGPUMesh); |
| 313 | coverage = get_framed_coverage(gpuOutset, gpuOutsetCoverage, |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 314 | gpuInset, gpuInsetCoverage, gpuDomain, |
| 315 | pixelCenter); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 316 | } |
| 317 | |
| 318 | SkRect pixelRect = SkRect::MakeXYWH(px, py, 1.f, 1.f); |
| 319 | pixelRect.inset(0.1f, 0.1f); |
| 320 | |
| 321 | SkScalar a = 1.f - 0.5f * coverage; |
| 322 | pixelPaint.setColor4f({a, a, a, 1.f}, nullptr); |
| 323 | canvas->drawRect(pixelRect, pixelPaint); |
| 324 | |
| 325 | pixelPaint.setColor(coverage > 0.f ? SK_ColorGREEN : SK_ColorRED); |
| 326 | pixelRect.inset(0.38f, 0.38f); |
| 327 | canvas->drawRect(pixelRect, pixelPaint); |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | linePaint.setPathEffect(dashes); |
| 332 | // Draw the inset/outset "infinite" lines |
| 333 | if (fCoverageMode == CoverageMode::kEdgeDistance) { |
| 334 | for (int i = 0; i < 4; ++i) { |
| 335 | if (fEdgeAA[i]) { |
| 336 | linePaint.setColor(SK_ColorBLUE); |
| 337 | draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]); |
| 338 | linePaint.setColor(SK_ColorGREEN); |
| 339 | draw_extended_line(canvas, linePaint, insets[i * 2], insets[i * 2 + 1]); |
| 340 | } else { |
| 341 | // Both outset and inset are the same line, so only draw one in cyan |
| 342 | linePaint.setColor(SK_ColorCYAN); |
| 343 | draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]); |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | linePaint.setPathEffect(nullptr); |
| 349 | // What is tessellated using GrQuadPerEdgeAA |
| 350 | if (fCoverageMode == CoverageMode::kGPUMesh) { |
| 351 | SkPath outsetPath; |
| 352 | outsetPath.addPoly(gpuOutset, 4, true); |
| 353 | linePaint.setColor(SK_ColorBLUE); |
| 354 | canvas->drawPath(outsetPath, linePaint); |
| 355 | |
| 356 | SkPath insetPath; |
| 357 | insetPath.addPoly(gpuInset, 4, true); |
| 358 | linePaint.setColor(SK_ColorGREEN); |
| 359 | canvas->drawPath(insetPath, linePaint); |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 360 | |
| 361 | SkPaint domainPaint = linePaint; |
| 362 | domainPaint.setStrokeWidth(2.f / kViewScale); |
| 363 | domainPaint.setPathEffect(dashes); |
| 364 | domainPaint.setColor(SK_ColorMAGENTA); |
| 365 | canvas->drawRect(gpuDomain, domainPaint); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 366 | } |
| 367 | |
| 368 | // Draw the edges of the true quad as a solid line |
| 369 | SkPath path; |
| 370 | path.addPoly(fCorners, 4, true); |
| 371 | linePaint.setColor(SK_ColorBLACK); |
| 372 | canvas->drawPath(path, linePaint); |
| 373 | } else { |
| 374 | // Draw the edges of the true quad as a solid *red* line |
| 375 | SkPath path; |
| 376 | path.addPoly(fCorners, 4, true); |
| 377 | linePaint.setColor(SK_ColorRED); |
| 378 | linePaint.setPathEffect(nullptr); |
| 379 | canvas->drawPath(path, linePaint); |
| 380 | } |
| 381 | |
| 382 | // Draw the four clickable corners as circles |
| 383 | circlePaint.setColor(valid ? SK_ColorBLACK : SK_ColorRED); |
| 384 | for (int i = 0; i < 4; ++i) { |
| 385 | canvas->drawCircle(fCorners[i], 5.f / kViewScale, circlePaint); |
| 386 | } |
| 387 | } |
| 388 | |
Hal Canary | b1f411a | 2019-08-29 10:39:22 -0400 | [diff] [blame] | 389 | Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 390 | bool onClick(Sample::Click*) override; |
Hal Canary | 6cc65e1 | 2019-07-03 15:53:04 -0400 | [diff] [blame] | 391 | bool onChar(SkUnichar) override; |
Hal Canary | 8a02731 | 2019-07-03 10:55:44 -0400 | [diff] [blame] | 392 | SkString name() override { return SkString("DegenerateQuad"); } |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 393 | |
| 394 | private: |
| 395 | class Click; |
| 396 | |
| 397 | enum class CoverageMode { |
| 398 | kArea, kEdgeDistance, kGPUMesh |
| 399 | }; |
| 400 | |
| 401 | const SkRect fOuterRect; |
| 402 | SkPoint fCorners[4]; // TL, TR, BR, BL |
| 403 | bool fEdgeAA[4]; // T, R, B, L |
| 404 | CoverageMode fCoverageMode; |
| 405 | |
| 406 | bool isValid() const { |
| 407 | SkPath path; |
| 408 | path.addPoly(fCorners, 4, true); |
| 409 | return path.isConvex(); |
| 410 | } |
| 411 | |
| 412 | void getTessellatedPoints(SkPoint inset[4], SkScalar insetCoverage[4], SkPoint outset[4], |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 413 | SkScalar outsetCoverage[4], SkRect* domain) const { |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 414 | // Fixed vertex spec for extracting the picture frame geometry |
Robert Phillips | ef80d7b | 2021-09-14 16:10:56 -0400 | [diff] [blame] | 415 | static const VertexSpec kSpec = |
| 416 | {GrQuad::Type::kGeneral, ColorType::kNone, |
| 417 | GrQuad::Type::kAxisAligned, false, Subset::kNo, |
| 418 | GrAAType::kCoverage, false, IndexBufferOption::kPictureFramed}; |
Michael Ludwig | de4c58c | 2019-06-04 09:12:59 -0400 | [diff] [blame] | 419 | static const GrQuad kIgnored(SkRect::MakeEmpty()); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 420 | |
| 421 | GrQuadAAFlags flags = GrQuadAAFlags::kNone; |
| 422 | flags |= fEdgeAA[0] ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone; |
| 423 | flags |= fEdgeAA[1] ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone; |
| 424 | flags |= fEdgeAA[2] ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone; |
| 425 | flags |= fEdgeAA[3] ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone; |
| 426 | |
Michael Ludwig | de4c58c | 2019-06-04 09:12:59 -0400 | [diff] [blame] | 427 | GrQuad quad = GrQuad::MakeFromSkQuad(fCorners, SkMatrix::I()); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 428 | |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 429 | float vertices[56]; // 2 quads, with x, y, coverage, and geometry domain (7 floats x 8 vert) |
Robert Phillips | ef80d7b | 2021-09-14 16:10:56 -0400 | [diff] [blame] | 430 | skgpu::v1::QuadPerEdgeAA::Tessellator tessellator(kSpec, (char*) vertices); |
Michael Ludwig | 704d540 | 2019-11-25 09:43:37 -0500 | [diff] [blame] | 431 | tessellator.append(&quad, nullptr, {1.f, 1.f, 1.f, 1.f}, |
Michael Ludwig | 73dbea6 | 2019-11-19 14:55:36 -0500 | [diff] [blame] | 432 | SkRect::MakeEmpty(), flags); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 433 | |
| 434 | // The first quad in vertices is the inset, then the outset, but they |
| 435 | // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange |
| 436 | inset[0] = {vertices[0], vertices[1]}; // TL |
| 437 | insetCoverage[0] = vertices[2]; |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 438 | inset[3] = {vertices[7], vertices[8]}; // BL |
| 439 | insetCoverage[3] = vertices[9]; |
| 440 | inset[1] = {vertices[14], vertices[15]}; // TR |
| 441 | insetCoverage[1] = vertices[16]; |
| 442 | inset[2] = {vertices[21], vertices[22]}; // BR |
| 443 | insetCoverage[2] = vertices[23]; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 444 | |
Michael Ludwig | d3aeecd | 2019-04-25 12:40:07 -0400 | [diff] [blame] | 445 | outset[0] = {vertices[28], vertices[29]}; // TL |
| 446 | outsetCoverage[0] = vertices[30]; |
| 447 | outset[3] = {vertices[35], vertices[36]}; // BL |
| 448 | outsetCoverage[3] = vertices[37]; |
| 449 | outset[1] = {vertices[42], vertices[43]}; // TR |
| 450 | outsetCoverage[1] = vertices[44]; |
| 451 | outset[2] = {vertices[49], vertices[50]}; // BR |
| 452 | outsetCoverage[2] = vertices[51]; |
| 453 | |
| 454 | *domain = {vertices[52], vertices[53], vertices[54], vertices[55]}; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 455 | } |
| 456 | |
John Stiles | 7571f9e | 2020-09-02 22:42:33 -0400 | [diff] [blame] | 457 | using INHERITED = Sample; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 458 | }; |
| 459 | |
| 460 | class DegenerateQuadSample::Click : public Sample::Click { |
| 461 | public: |
Hal Canary | fcf6359 | 2019-07-12 11:32:43 -0400 | [diff] [blame] | 462 | Click(const SkRect& clamp, int index) |
| 463 | : fOuterRect(clamp) |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 464 | , fIndex(index) {} |
| 465 | |
| 466 | void doClick(SkPoint points[4]) { |
| 467 | if (fIndex >= 0) { |
| 468 | this->drag(&points[fIndex]); |
| 469 | } else { |
| 470 | for (int i = 0; i < 4; ++i) { |
| 471 | this->drag(&points[i]); |
| 472 | } |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | private: |
| 477 | SkRect fOuterRect; |
| 478 | int fIndex; |
| 479 | |
| 480 | void drag(SkPoint* point) { |
Hal Canary | fcf6359 | 2019-07-12 11:32:43 -0400 | [diff] [blame] | 481 | SkPoint delta = fCurr - fPrev; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 482 | *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale); |
Brian Osman | 116b33e | 2020-02-05 13:34:09 -0500 | [diff] [blame] | 483 | point->fX = std::min(fOuterRect.fRight, std::max(point->fX, fOuterRect.fLeft)); |
| 484 | point->fY = std::min(fOuterRect.fBottom, std::max(point->fY, fOuterRect.fTop)); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 485 | } |
| 486 | }; |
| 487 | |
Hal Canary | b1f411a | 2019-08-29 10:39:22 -0400 | [diff] [blame] | 488 | Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) { |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 489 | SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale); |
| 490 | for (int i = 0; i < 4; ++i) { |
| 491 | if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) { |
Hal Canary | fcf6359 | 2019-07-12 11:32:43 -0400 | [diff] [blame] | 492 | return new Click(fOuterRect, i); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 493 | } |
| 494 | } |
Hal Canary | fcf6359 | 2019-07-12 11:32:43 -0400 | [diff] [blame] | 495 | return new Click(fOuterRect, -1); |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 496 | } |
| 497 | |
| 498 | bool DegenerateQuadSample::onClick(Sample::Click* click) { |
| 499 | Click* myClick = (Click*) click; |
| 500 | myClick->doClick(fCorners); |
| 501 | return true; |
| 502 | } |
| 503 | |
Hal Canary | 6cc65e1 | 2019-07-03 15:53:04 -0400 | [diff] [blame] | 504 | bool DegenerateQuadSample::onChar(SkUnichar code) { |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 505 | switch(code) { |
| 506 | case '1': |
| 507 | fEdgeAA[0] = !fEdgeAA[0]; |
| 508 | return true; |
| 509 | case '2': |
| 510 | fEdgeAA[1] = !fEdgeAA[1]; |
| 511 | return true; |
| 512 | case '3': |
| 513 | fEdgeAA[2] = !fEdgeAA[2]; |
| 514 | return true; |
| 515 | case '4': |
| 516 | fEdgeAA[3] = !fEdgeAA[3]; |
| 517 | return true; |
| 518 | case 'q': |
| 519 | fCoverageMode = CoverageMode::kArea; |
| 520 | return true; |
| 521 | case 'w': |
| 522 | fCoverageMode = CoverageMode::kEdgeDistance; |
| 523 | return true; |
| 524 | case 'e': |
| 525 | fCoverageMode = CoverageMode::kGPUMesh; |
| 526 | return true; |
| 527 | } |
Hal Canary | 6cc65e1 | 2019-07-03 15:53:04 -0400 | [diff] [blame] | 528 | return false; |
Michael Ludwig | e6266a2 | 2019-03-07 11:24:32 -0500 | [diff] [blame] | 529 | } |
| 530 | |
| 531 | DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));) |