1 /*
2 * Copyright 2011 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 #include "src/gpu/ganesh/ops/AAHairLinePathRenderer.h"
8
9 #include "include/core/SkMatrix.h"
10 #include "include/core/SkPaint.h"
11 #include "include/core/SkPath.h"
12 #include "include/core/SkPoint3.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkString.h"
17 #include "include/core/SkStrokeRec.h"
18 #include "include/gpu/ganesh/GrRecordingContext.h"
19 #include "include/private/SkColorData.h"
20 #include "include/private/base/SkAlignedStorage.h"
21 #include "include/private/base/SkAssert.h"
22 #include "include/private/base/SkDebug.h"
23 #include "include/private/base/SkFloatingPoint.h"
24 #include "include/private/base/SkMacros.h"
25 #include "include/private/base/SkMath.h"
26 #include "include/private/base/SkOnce.h"
27 #include "include/private/base/SkPoint_impl.h"
28 #include "include/private/base/SkTArray.h"
29 #include "include/private/gpu/ganesh/GrTypesPriv.h"
30 #include "src/base/SkSafeMath.h"
31 #include "src/core/SkGeometry.h"
32 #include "src/core/SkMatrixPriv.h"
33 #include "src/core/SkPointPriv.h"
34 #include "src/gpu/ResourceKey.h"
35 #include "src/gpu/ganesh/GrAppliedClip.h"
36 #include "src/gpu/ganesh/GrAuditTrail.h"
37 #include "src/gpu/ganesh/GrBuffer.h"
38 #include "src/gpu/ganesh/GrCaps.h"
39 #include "src/gpu/ganesh/GrColor.h"
40 #include "src/gpu/ganesh/GrDefaultGeoProcFactory.h"
41 #include "src/gpu/ganesh/GrDrawOpTest.h"
42 #include "src/gpu/ganesh/GrGeometryProcessor.h"
43 #include "src/gpu/ganesh/GrMeshDrawTarget.h"
44 #include "src/gpu/ganesh/GrOpFlushState.h"
45 #include "src/gpu/ganesh/GrPaint.h"
46 #include "src/gpu/ganesh/GrProcessorAnalysis.h"
47 #include "src/gpu/ganesh/GrProcessorSet.h"
48 #include "src/gpu/ganesh/GrProgramInfo.h"
49 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
50 #include "src/gpu/ganesh/GrRenderTargetProxy.h"
51 #include "src/gpu/ganesh/GrResourceProvider.h"
52 #include "src/gpu/ganesh/GrShaderCaps.h"
53 #include "src/gpu/ganesh/GrSimpleMesh.h"
54 #include "src/gpu/ganesh/GrStyle.h"
55 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
56 #include "src/gpu/ganesh/GrTestUtils.h"
57 #include "src/gpu/ganesh/GrUtil.h"
58 #include "src/gpu/ganesh/SurfaceDrawContext.h"
59 #include "src/gpu/ganesh/effects/GrBezierEffect.h"
60 #include "src/gpu/ganesh/geometry/GrPathUtils.h"
61 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
62 #include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
63 #include "src/gpu/ganesh/ops/GrOp.h"
64 #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h"
65 #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
66
67 #include <algorithm>
68 #include <array>
69 #include <cstdint>
70 #include <cstring>
71 #include <utility>
72
73 class GrDstProxyView;
74 class GrPipeline;
75 class SkArenaAlloc;
76 class SkRandom;
77 enum class GrXferBarrierFlags;
78 struct GrUserStencilSettings;
79
80 using namespace skia_private;
81
82 #define PREALLOC_PTARRAY(N) STArray<(N),SkPoint, true>
83
84 using PtArray = TArray<SkPoint, true>;
85 using IntArray = TArray<int, true>;
86 using FloatArray = TArray<float, true>;
87
88 namespace {
89
90 // quadratics are rendered as 5-sided polys in order to bound the
91 // AA stroke around the center-curve. See comments in push_quad_index_buffer and
92 // bloat_quad. Quadratics and conics share an index buffer
93
94 // lines are rendered as:
95 // *______________*
96 // |\ -_______ /|
97 // | \ \ / |
98 // | *--------* |
99 // | / ______/ \ |
100 // */_-__________\*
101 // For: 6 vertices and 18 indices (for 6 triangles)
102
103 // Each quadratic is rendered as a five sided polygon. This poly bounds
104 // the quadratic's bounding triangle but has been expanded so that the
105 // 1-pixel wide area around the curve is inside the poly.
106 // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
107 // that is rendered would look like this:
108 // b0
109 // b
110 //
111 // a0 c0
112 // a c
113 // a1 c1
114 // Each is drawn as three triangles ((a0,a1,b0), (b0,c1,c0), (a1,c1,b0))
115 // specified by these 9 indices:
116 static const uint16_t kQuadIdxBufPattern[] = {
117 0, 1, 2,
118 2, 4, 3,
119 1, 4, 2
120 };
121
122 static const int kIdxsPerQuad = std::size(kQuadIdxBufPattern);
123 static const int kQuadNumVertices = 5;
124 static const int kQuadsNumInIdxBuffer = 256;
125 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey);
126
get_quads_index_buffer(GrResourceProvider * resourceProvider)127 sk_sp<const GrBuffer> get_quads_index_buffer(GrResourceProvider* resourceProvider) {
128 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey);
129 return resourceProvider->findOrCreatePatternedIndexBuffer(
130 kQuadIdxBufPattern, kIdxsPerQuad, kQuadsNumInIdxBuffer, kQuadNumVertices,
131 gQuadsIndexBufferKey);
132 }
133
134
135 // Each line segment is rendered as two quads and two triangles.
136 // p0 and p1 have alpha = 1 while all other points have alpha = 0.
137 // The four external points are offset 1 pixel perpendicular to the
138 // line and half a pixel parallel to the line.
139 //
140 // p4 p5
141 // p0 p1
142 // p2 p3
143 //
144 // Each is drawn as six triangles specified by these 18 indices:
145
146 static const uint16_t kLineSegIdxBufPattern[] = {
147 0, 1, 3,
148 0, 3, 2,
149 0, 4, 5,
150 0, 5, 1,
151 0, 2, 4,
152 1, 5, 3
153 };
154
155 static const int kIdxsPerLineSeg = std::size(kLineSegIdxBufPattern);
156 static const int kLineSegNumVertices = 6;
157 static const int kLineSegsNumInIdxBuffer = 256;
158
159 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey);
160
get_lines_index_buffer(GrResourceProvider * resourceProvider)161 sk_sp<const GrBuffer> get_lines_index_buffer(GrResourceProvider* resourceProvider) {
162 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey);
163 return resourceProvider->findOrCreatePatternedIndexBuffer(
164 kLineSegIdxBufPattern, kIdxsPerLineSeg, kLineSegsNumInIdxBuffer, kLineSegNumVertices,
165 gLinesIndexBufferKey);
166 }
167
168 // Takes 178th time of logf on Z600 / VC2010
get_float_exp(float x)169 int get_float_exp(float x) {
170 static_assert(sizeof(int) == sizeof(float));
171 #ifdef SK_DEBUG
172 static bool tested;
173 if (!tested) {
174 tested = true;
175 SkASSERT(get_float_exp(0.25f) == -2);
176 SkASSERT(get_float_exp(0.3f) == -2);
177 SkASSERT(get_float_exp(0.5f) == -1);
178 SkASSERT(get_float_exp(1.f) == 0);
179 SkASSERT(get_float_exp(2.f) == 1);
180 SkASSERT(get_float_exp(2.5f) == 1);
181 SkASSERT(get_float_exp(8.f) == 3);
182 SkASSERT(get_float_exp(100.f) == 6);
183 SkASSERT(get_float_exp(1000.f) == 9);
184 SkASSERT(get_float_exp(1024.f) == 10);
185 SkASSERT(get_float_exp(3000000.f) == 21);
186 }
187 #endif
188 const int* iptr = (const int*)&x;
189 return (((*iptr) & 0x7f800000) >> 23) - 127;
190 }
191
192 // Uses the max curvature function for quads to estimate
193 // where to chop the conic. If the max curvature is not
194 // found along the curve segment it will return 1 and
195 // dst[0] is the original conic. If it returns 2 the dst[0]
196 // and dst[1] are the two new conics.
split_conic(const SkPoint src[3],SkConic dst[2],const SkScalar weight)197 int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
198 SkScalar t = SkFindQuadMaxCurvature(src);
199 // SkFindQuadMaxCurvature() returns either a value in [0, 1) or NaN.
200 // However, passing NaN to conic.chopAt() will assert. Checking to see if
201 // t is in (0,1) will also cover the NaN case since NaN comparisons are always
202 // false, so we'll drop down into the else block in that case.
203 if (0 < t && t < 1) {
204 if (dst) {
205 SkConic conic;
206 conic.set(src, weight);
207 if (!conic.chopAt(t, dst)) {
208 dst[0].set(src, weight);
209 return 1;
210 }
211 }
212 return 2;
213 } else {
214 if (dst) {
215 dst[0].set(src, weight);
216 }
217 return 1;
218 }
219 }
220
221 // Calls split_conic on the entire conic and then once more on each subsection.
222 // Most cases will result in either 1 conic (chop point is not within t range)
223 // or 3 points (split once and then one subsection is split again).
chop_conic(const SkPoint src[3],SkConic dst[4],const SkScalar weight)224 int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
225 SkConic dstTemp[2];
226 int conicCnt = split_conic(src, dstTemp, weight);
227 if (2 == conicCnt) {
228 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
229 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
230 } else {
231 dst[0] = dstTemp[0];
232 }
233 return conicCnt;
234 }
235
236 // returns 0 if quad/conic is degen or close to it
237 // in this case approx the path with lines
238 // otherwise returns 1
is_degen_quad_or_conic(const SkPoint p[3],SkScalar * dsqd)239 int is_degen_quad_or_conic(const SkPoint p[3], SkScalar* dsqd) {
240 static const SkScalar gDegenerateToLineTol = GrPathUtils::kDefaultTolerance;
241 static const SkScalar gDegenerateToLineTolSqd =
242 gDegenerateToLineTol * gDegenerateToLineTol;
243
244 if (SkPointPriv::DistanceToSqd(p[0], p[1]) < gDegenerateToLineTolSqd ||
245 SkPointPriv::DistanceToSqd(p[1], p[2]) < gDegenerateToLineTolSqd) {
246 return 1;
247 }
248
249 *dsqd = SkPointPriv::DistanceToLineBetweenSqd(p[1], p[0], p[2]);
250 if (*dsqd < gDegenerateToLineTolSqd) {
251 return 1;
252 }
253
254 if (SkPointPriv::DistanceToLineBetweenSqd(p[2], p[1], p[0]) < gDegenerateToLineTolSqd) {
255 return 1;
256 }
257 return 0;
258 }
259
is_degen_quad_or_conic(const SkPoint p[3])260 int is_degen_quad_or_conic(const SkPoint p[3]) {
261 SkScalar dsqd;
262 return is_degen_quad_or_conic(p, &dsqd);
263 }
264
265 // we subdivide the quads to avoid huge overfill
266 // if it returns -1 then should be drawn as lines
num_quad_subdivs(const SkPoint p[3])267 int num_quad_subdivs(const SkPoint p[3]) {
268 SkScalar dsqd;
269 if (is_degen_quad_or_conic(p, &dsqd)) {
270 return -1;
271 }
272
273 // tolerance of triangle height in pixels
274 // tuned on windows Quadro FX 380 / Z600
275 // trade off of fill vs cpu time on verts
276 // maybe different when do this using gpu (geo or tess shaders)
277 static const SkScalar gSubdivTol = 175 * SK_Scalar1;
278
279 if (dsqd <= gSubdivTol * gSubdivTol) {
280 return 0;
281 } else {
282 static const int kMaxSub = 4;
283 // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
284 // = log4(d*d/tol*tol)/2
285 // = log2(d*d/tol*tol)
286
287 // +1 since we're ignoring the mantissa contribution.
288 int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
289 log = std::min(std::max(0, log), kMaxSub);
290 return log;
291 }
292 }
293
294 /**
295 * Generates the lines and quads to be rendered. Lines are always recorded in
296 * device space. We will do a device space bloat to account for the 1pixel
297 * thickness.
298 * Quads are recorded in device space unless m contains
299 * perspective, then in they are in src space. We do this because we will
300 * subdivide large quads to reduce over-fill. This subdivision has to be
301 * performed before applying the perspective matrix.
302 */
gather_lines_and_quads(const SkPath & path,const SkMatrix & m,const SkIRect & devClipBounds,SkScalar capLength,bool convertConicsToQuads,PtArray * lines,PtArray * quads,PtArray * conics,IntArray * quadSubdivCnts,FloatArray * conicWeights)303 int gather_lines_and_quads(const SkPath& path,
304 const SkMatrix& m,
305 const SkIRect& devClipBounds,
306 SkScalar capLength,
307 bool convertConicsToQuads,
308 PtArray* lines,
309 PtArray* quads,
310 PtArray* conics,
311 IntArray* quadSubdivCnts,
312 FloatArray* conicWeights) {
313 SkPath::Iter iter(path, false);
314
315 int totalQuadCount = 0;
316 SkRect bounds;
317 SkIRect ibounds;
318
319 bool persp = m.hasPerspective();
320
321 // Whenever a degenerate, zero-length contour is encountered, this code will insert a
322 // 'capLength' x-aligned line segment. Since this is rendering hairlines it is hoped this will
323 // suffice for AA square & circle capping.
324 int verbsInContour = 0; // Does not count moves
325 bool seenZeroLengthVerb = false;
326 SkPoint zeroVerbPt;
327
328 // Adds a quad that has already been chopped to the list and checks for quads that are close to
329 // lines. Also does a bounding box check. It takes points that are in src space and device
330 // space. The src points are only required if the view matrix has perspective.
331 auto addChoppedQuad = [&](const SkPoint srcPts[3], const SkPoint devPts[4],
332 bool isContourStart) {
333 SkRect bounds;
334 SkIRect ibounds;
335 bounds.setBounds(devPts, 3);
336 bounds.outset(SK_Scalar1, SK_Scalar1);
337 bounds.roundOut(&ibounds);
338 // We only need the src space space pts when not in perspective.
339 SkASSERT(srcPts || !persp);
340 if (SkIRect::Intersects(devClipBounds, ibounds)) {
341 int subdiv = num_quad_subdivs(devPts);
342 SkASSERT(subdiv >= -1);
343 if (-1 == subdiv) {
344 SkPoint* pts = lines->push_back_n(4);
345 pts[0] = devPts[0];
346 pts[1] = devPts[1];
347 pts[2] = devPts[1];
348 pts[3] = devPts[2];
349 if (isContourStart && pts[0] == pts[1] && pts[2] == pts[3]) {
350 seenZeroLengthVerb = true;
351 zeroVerbPt = pts[0];
352 }
353 } else {
354 // when in perspective keep quads in src space
355 const SkPoint* qPts = persp ? srcPts : devPts;
356 SkPoint* pts = quads->push_back_n(3);
357 pts[0] = qPts[0];
358 pts[1] = qPts[1];
359 pts[2] = qPts[2];
360 quadSubdivCnts->push_back() = subdiv;
361 totalQuadCount += 1 << subdiv;
362 }
363 }
364 };
365
366 // Applies the view matrix to quad src points and calls the above helper.
367 auto addSrcChoppedQuad = [&](const SkPoint srcSpaceQuadPts[3], bool isContourStart) {
368 SkPoint devPts[3];
369 m.mapPoints(devPts, srcSpaceQuadPts, 3);
370 addChoppedQuad(srcSpaceQuadPts, devPts, isContourStart);
371 };
372
373 SkPoint pathPts[4] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
374 for (;;) {
375 SkPath::Verb verb = iter.next(pathPts);
376 switch (verb) {
377 case SkPath::kConic_Verb:
378 if (convertConicsToQuads) {
379 SkScalar weight = iter.conicWeight();
380 SkAutoConicToQuads converter;
381 const SkPoint* quadPts = converter.computeQuads(pathPts, weight, 0.25f);
382 for (int i = 0; i < converter.countQuads(); ++i) {
383 addSrcChoppedQuad(quadPts + 2 * i, !verbsInContour && 0 == i);
384 }
385 } else {
386 SkConic dst[4];
387 // We chop the conics to create tighter clipping to hide error
388 // that appears near max curvature of very thin conics. Thin
389 // hyperbolas with high weight still show error.
390 int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
391 for (int i = 0; i < conicCnt; ++i) {
392 SkPoint devPts[4];
393 SkPoint* chopPnts = dst[i].fPts;
394 m.mapPoints(devPts, chopPnts, 3);
395 bounds.setBounds(devPts, 3);
396 bounds.outset(SK_Scalar1, SK_Scalar1);
397 bounds.roundOut(&ibounds);
398 if (SkIRect::Intersects(devClipBounds, ibounds)) {
399 if (is_degen_quad_or_conic(devPts)) {
400 SkPoint* pts = lines->push_back_n(4);
401 pts[0] = devPts[0];
402 pts[1] = devPts[1];
403 pts[2] = devPts[1];
404 pts[3] = devPts[2];
405 if (verbsInContour == 0 && i == 0 && pts[0] == pts[1] &&
406 pts[2] == pts[3]) {
407 seenZeroLengthVerb = true;
408 zeroVerbPt = pts[0];
409 }
410 } else {
411 // when in perspective keep conics in src space
412 SkPoint* cPts = persp ? chopPnts : devPts;
413 SkPoint* pts = conics->push_back_n(3);
414 pts[0] = cPts[0];
415 pts[1] = cPts[1];
416 pts[2] = cPts[2];
417 conicWeights->push_back() = dst[i].fW;
418 }
419 }
420 }
421 }
422 verbsInContour++;
423 break;
424 case SkPath::kMove_Verb:
425 // New contour (and last one was unclosed). If it was just a zero length drawing
426 // operation, and we're supposed to draw caps, then add a tiny line.
427 if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) {
428 SkPoint* pts = lines->push_back_n(2);
429 pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY);
430 pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY);
431 }
432 verbsInContour = 0;
433 seenZeroLengthVerb = false;
434 break;
435 case SkPath::kLine_Verb: {
436 SkPoint devPts[2];
437 m.mapPoints(devPts, pathPts, 2);
438 bounds.setBounds(devPts, 2);
439 bounds.outset(SK_Scalar1, SK_Scalar1);
440 bounds.roundOut(&ibounds);
441 if (SkIRect::Intersects(devClipBounds, ibounds)) {
442 SkPoint* pts = lines->push_back_n(2);
443 pts[0] = devPts[0];
444 pts[1] = devPts[1];
445 if (verbsInContour == 0 && pts[0] == pts[1]) {
446 seenZeroLengthVerb = true;
447 zeroVerbPt = pts[0];
448 }
449 }
450 verbsInContour++;
451 break;
452 }
453 case SkPath::kQuad_Verb: {
454 SkPoint choppedPts[5];
455 // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
456 // When it is degenerate it allows the approximation with lines to work since the
457 // chop point (if there is one) will be at the parabola's vertex. In the nearly
458 // degenerate the QuadUVMatrix computed for the points is almost singular which
459 // can cause rendering artifacts.
460 int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
461 for (int i = 0; i < n; ++i) {
462 addSrcChoppedQuad(choppedPts + i * 2, !verbsInContour && 0 == i);
463 }
464 verbsInContour++;
465 break;
466 }
467 case SkPath::kCubic_Verb: {
468 SkPoint devPts[4];
469 m.mapPoints(devPts, pathPts, 4);
470 bounds.setBounds(devPts, 4);
471 bounds.outset(SK_Scalar1, SK_Scalar1);
472 bounds.roundOut(&ibounds);
473 if (SkIRect::Intersects(devClipBounds, ibounds)) {
474 PREALLOC_PTARRAY(32) q;
475 // We convert cubics to quadratics (for now).
476 // In perspective have to do conversion in src space.
477 if (persp) {
478 SkScalar tolScale =
479 GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds());
480 GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q);
481 } else {
482 GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q);
483 }
484 for (int i = 0; i < q.size(); i += 3) {
485 if (persp) {
486 addSrcChoppedQuad(&q[i], !verbsInContour && 0 == i);
487 } else {
488 addChoppedQuad(nullptr, &q[i], !verbsInContour && 0 == i);
489 }
490 }
491 }
492 verbsInContour++;
493 break;
494 }
495 case SkPath::kClose_Verb:
496 // Contour is closed, so we don't need to grow the starting line, unless it's
497 // *just* a zero length subpath. (SVG Spec 11.4, 'stroke').
498 if (capLength > 0) {
499 if (seenZeroLengthVerb && verbsInContour == 1) {
500 SkPoint* pts = lines->push_back_n(2);
501 pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY);
502 pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY);
503 } else if (verbsInContour == 0) {
504 // Contour was (moveTo, close). Add a line.
505 SkPoint devPts[2];
506 m.mapPoints(devPts, pathPts, 1);
507 devPts[1] = devPts[0];
508 bounds.setBounds(devPts, 2);
509 bounds.outset(SK_Scalar1, SK_Scalar1);
510 bounds.roundOut(&ibounds);
511 if (SkIRect::Intersects(devClipBounds, ibounds)) {
512 SkPoint* pts = lines->push_back_n(2);
513 pts[0] = SkPoint::Make(devPts[0].fX - capLength, devPts[0].fY);
514 pts[1] = SkPoint::Make(devPts[1].fX + capLength, devPts[1].fY);
515 }
516 }
517 }
518 break;
519 case SkPath::kDone_Verb:
520 if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) {
521 // Path ended with a dangling (moveTo, line|quad|etc). If the final verb is
522 // degenerate, we need to draw a line.
523 SkPoint* pts = lines->push_back_n(2);
524 pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY);
525 pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY);
526 }
527 return totalQuadCount;
528 }
529 }
530 }
531
532 struct LineVertex {
533 SkPoint fPos;
534 float fCoverage;
535 };
536
537 struct BezierVertex {
538 SkPoint fPos;
539 union {
540 struct {
541 SkScalar fKLM[3];
542 } fConic;
543 SkVector fQuadCoord;
544 struct {
545 SkScalar fBogus[4];
546 };
547 };
548 };
549
550 static_assert(sizeof(BezierVertex) == 3 * sizeof(SkPoint));
551
intersect_lines(const SkPoint & ptA,const SkVector & normA,const SkPoint & ptB,const SkVector & normB,SkPoint * result)552 void intersect_lines(const SkPoint& ptA, const SkVector& normA,
553 const SkPoint& ptB, const SkVector& normB,
554 SkPoint* result) {
555
556 SkScalar lineAW = -normA.dot(ptA);
557 SkScalar lineBW = -normB.dot(ptB);
558
559 SkScalar wInv = normA.fX * normB.fY - normA.fY * normB.fX;
560 wInv = sk_ieee_float_divide(1.0f, wInv);
561 if (!SkIsFinite(wInv)) {
562 // lines are parallel, pick the point in between
563 *result = (ptA + ptB)*SK_ScalarHalf;
564 *result += normA;
565 } else {
566 result->fX = normA.fY * lineBW - lineAW * normB.fY;
567 result->fX *= wInv;
568
569 result->fY = lineAW * normB.fX - normA.fX * lineBW;
570 result->fY *= wInv;
571 }
572 }
573
set_uv_quad(const SkPoint qpts[3],BezierVertex verts[kQuadNumVertices])574 void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kQuadNumVertices]) {
575 // this should be in the src space, not dev coords, when we have perspective
576 GrPathUtils::QuadUVMatrix DevToUV(qpts);
577 DevToUV.apply(verts, kQuadNumVertices, sizeof(BezierVertex), sizeof(SkPoint));
578 }
579
bloat_quad(const SkPoint qpts[3],const SkMatrix * toDevice,const SkMatrix * toSrc,BezierVertex verts[kQuadNumVertices])580 bool bloat_quad(const SkPoint qpts[3],
581 const SkMatrix* toDevice,
582 const SkMatrix* toSrc,
583 BezierVertex verts[kQuadNumVertices]) {
584 SkASSERT(!toDevice == !toSrc);
585 // original quad is specified by tri a,b,c
586 SkPoint a = qpts[0];
587 SkPoint b = qpts[1];
588 SkPoint c = qpts[2];
589
590 if (toDevice) {
591 toDevice->mapPoints(&a, 1);
592 toDevice->mapPoints(&b, 1);
593 toDevice->mapPoints(&c, 1);
594 }
595 // make a new poly where we replace a and c by a 1-pixel wide edges orthog
596 // to edges ab and bc:
597 //
598 // before | after
599 // | b0
600 // b |
601 // |
602 // | a0 c0
603 // a c | a1 c1
604 //
605 // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
606 // respectively.
607 BezierVertex& a0 = verts[0];
608 BezierVertex& a1 = verts[1];
609 BezierVertex& b0 = verts[2];
610 BezierVertex& c0 = verts[3];
611 BezierVertex& c1 = verts[4];
612
613 SkVector ab = b;
614 ab -= a;
615 SkVector ac = c;
616 ac -= a;
617 SkVector cb = b;
618 cb -= c;
619
620 // After the transform (or due to floating point math) we might have a line,
621 // try to do something reasonable
622
623 bool abNormalized = ab.normalize();
624 bool cbNormalized = cb.normalize();
625
626 if (!abNormalized) {
627 if (!cbNormalized) {
628 return false; // Quad is degenerate so we won't add it.
629 }
630
631 ab = cb;
632 }
633
634 if (!cbNormalized) {
635 cb = ab;
636 }
637
638 // We should have already handled degenerates
639 SkASSERT(ab.length() > 0 && cb.length() > 0);
640
641 SkVector abN = SkPointPriv::MakeOrthog(ab, SkPointPriv::kLeft_Side);
642 if (abN.dot(ac) > 0) {
643 abN.negate();
644 }
645
646 SkVector cbN = SkPointPriv::MakeOrthog(cb, SkPointPriv::kLeft_Side);
647 if (cbN.dot(ac) < 0) {
648 cbN.negate();
649 }
650
651 a0.fPos = a;
652 a0.fPos += abN;
653 a1.fPos = a;
654 a1.fPos -= abN;
655
656 if (toDevice && SkPointPriv::LengthSqd(ac) <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) {
657 c = b;
658 }
659 c0.fPos = c;
660 c0.fPos += cbN;
661 c1.fPos = c;
662 c1.fPos -= cbN;
663
664 intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
665
666 if (toSrc) {
667 SkMatrixPriv::MapPointsWithStride(*toSrc, &verts[0].fPos, sizeof(BezierVertex),
668 kQuadNumVertices);
669 }
670
671 return true;
672 }
673
674 // Equations based off of Loop-Blinn Quadratic GPU Rendering
675 // Input Parametric:
676 // P(t) = (P0*(1-t)^2 + 2*w*P1*t*(1-t) + P2*t^2) / (1-t)^2 + 2*w*t*(1-t) + t^2)
677 // Output Implicit:
678 // f(x, y, w) = f(P) = K^2 - LM
679 // K = dot(k, P), L = dot(l, P), M = dot(m, P)
680 // k, l, m are calculated in function GrPathUtils::getConicKLM
set_conic_coeffs(const SkPoint p[3],BezierVertex verts[kQuadNumVertices],const SkScalar weight)681 void set_conic_coeffs(const SkPoint p[3],
682 BezierVertex verts[kQuadNumVertices],
683 const SkScalar weight) {
684 SkMatrix klm;
685
686 GrPathUtils::getConicKLM(p, weight, &klm);
687
688 for (int i = 0; i < kQuadNumVertices; ++i) {
689 const SkPoint3 pt3 = {verts[i].fPos.x(), verts[i].fPos.y(), 1.f};
690 klm.mapHomogeneousPoints((SkPoint3* ) verts[i].fConic.fKLM, &pt3, 1);
691 }
692 }
693
add_conics(const SkPoint p[3],const SkScalar weight,const SkMatrix * toDevice,const SkMatrix * toSrc,BezierVertex ** vert)694 void add_conics(const SkPoint p[3],
695 const SkScalar weight,
696 const SkMatrix* toDevice,
697 const SkMatrix* toSrc,
698 BezierVertex** vert) {
699 if (bloat_quad(p, toDevice, toSrc, *vert)) {
700 set_conic_coeffs(p, *vert, weight);
701 *vert += kQuadNumVertices;
702 }
703 }
704
add_quads(const SkPoint p[3],int subdiv,const SkMatrix * toDevice,const SkMatrix * toSrc,BezierVertex ** vert)705 void add_quads(const SkPoint p[3],
706 int subdiv,
707 const SkMatrix* toDevice,
708 const SkMatrix* toSrc,
709 BezierVertex** vert) {
710 SkASSERT(subdiv >= 0);
711 // temporary vertex storage to avoid reading the vertex buffer
712 BezierVertex outVerts[kQuadNumVertices] = {};
713
714 // storage for the chopped quad
715 // pts 0,1,2 are the first quad, and 2,3,4 the second quad
716 SkPoint choppedQuadPts[5];
717 // start off with our original curve in the second quad slot
718 memcpy(&choppedQuadPts[2], p, 3*sizeof(SkPoint));
719
720 int stepCount = 1 << subdiv;
721 while (stepCount > 1) {
722 // The general idea is:
723 // * chop the quad using pts 2,3,4 as the input
724 // * write out verts using pts 0,1,2
725 // * now 2,3,4 is the remainder of the curve, chop again until all subdivisions are done
726 SkScalar h = 1.f / stepCount;
727 SkChopQuadAt(&choppedQuadPts[2], choppedQuadPts, h);
728
729 if (bloat_quad(choppedQuadPts, toDevice, toSrc, outVerts)) {
730 set_uv_quad(choppedQuadPts, outVerts);
731 memcpy(*vert, outVerts, kQuadNumVertices * sizeof(BezierVertex));
732 *vert += kQuadNumVertices;
733 }
734 --stepCount;
735 }
736
737 // finish up, write out the final quad
738 if (bloat_quad(&choppedQuadPts[2], toDevice, toSrc, outVerts)) {
739 set_uv_quad(&choppedQuadPts[2], outVerts);
740 memcpy(*vert, outVerts, kQuadNumVertices * sizeof(BezierVertex));
741 *vert += kQuadNumVertices;
742 }
743 }
744
add_line(const SkPoint p[2],const SkMatrix * toSrc,uint8_t coverage,LineVertex ** vert)745 void add_line(const SkPoint p[2],
746 const SkMatrix* toSrc,
747 uint8_t coverage,
748 LineVertex** vert) {
749 const SkPoint& a = p[0];
750 const SkPoint& b = p[1];
751
752 SkVector ortho, vec = b;
753 vec -= a;
754
755 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
756
757 if (vec.setLength(SK_ScalarHalf)) {
758 // Create a vector orthogonal to 'vec' and of unit length
759 ortho.fX = 2.0f * vec.fY;
760 ortho.fY = -2.0f * vec.fX;
761
762 float floatCoverage = GrNormalizeByteToFloat(coverage);
763
764 if (lengthSqd >= 1.0f) {
765 // Relative to points a and b:
766 // The inner vertices are inset half a pixel along the line a,b
767 (*vert)[0].fPos = a + vec;
768 (*vert)[0].fCoverage = floatCoverage;
769 (*vert)[1].fPos = b - vec;
770 (*vert)[1].fCoverage = floatCoverage;
771 } else {
772 // The inner vertices are inset a distance of length(a,b) from the outer edge of
773 // geometry. For the "a" inset this is the same as insetting from b by half a pixel.
774 // The coverage is then modulated by the length. This gives us the correct
775 // coverage for rects shorter than a pixel as they get translated subpixel amounts
776 // inside of a pixel.
777 SkScalar length = SkScalarSqrt(lengthSqd);
778 (*vert)[0].fPos = b - vec;
779 (*vert)[0].fCoverage = floatCoverage * length;
780 (*vert)[1].fPos = a + vec;
781 (*vert)[1].fCoverage = floatCoverage * length;
782 }
783 // Relative to points a and b:
784 // The outer vertices are outset half a pixel along the line a,b and then a whole pixel
785 // orthogonally.
786 (*vert)[2].fPos = a - vec + ortho;
787 (*vert)[2].fCoverage = 0;
788 (*vert)[3].fPos = b + vec + ortho;
789 (*vert)[3].fCoverage = 0;
790 (*vert)[4].fPos = a - vec - ortho;
791 (*vert)[4].fCoverage = 0;
792 (*vert)[5].fPos = b + vec - ortho;
793 (*vert)[5].fCoverage = 0;
794
795 if (toSrc) {
796 SkMatrixPriv::MapPointsWithStride(*toSrc, &(*vert)->fPos, sizeof(LineVertex),
797 kLineSegNumVertices);
798 }
799 } else {
800 // just make it degenerate and likely offscreen
801 for (int i = 0; i < kLineSegNumVertices; ++i) {
802 (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax);
803 }
804 }
805
806 *vert += kLineSegNumVertices;
807 }
808
809 ///////////////////////////////////////////////////////////////////////////////
810
811 class AAHairlineOp final : public GrMeshDrawOp {
812 private:
813 using Helper = GrSimpleMeshDrawOpHelperWithStencil;
814
815 public:
816 DEFINE_OP_CLASS_ID
817
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkPath & path,const GrStyle & style,const SkIRect & devClipBounds,const GrUserStencilSettings * stencilSettings)818 static GrOp::Owner Make(GrRecordingContext* context,
819 GrPaint&& paint,
820 const SkMatrix& viewMatrix,
821 const SkPath& path,
822 const GrStyle& style,
823 const SkIRect& devClipBounds,
824 const GrUserStencilSettings* stencilSettings) {
825 SkScalar hairlineCoverage;
826 uint8_t newCoverage = 0xff;
827 if (GrIsStrokeHairlineOrEquivalent(style, viewMatrix, &hairlineCoverage)) {
828 newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
829 }
830
831 const SkStrokeRec& stroke = style.strokeRec();
832 SkScalar capLength = SkPaint::kButt_Cap != stroke.getCap() ? hairlineCoverage * 0.5f : 0.0f;
833
834 return Helper::FactoryHelper<AAHairlineOp>(context, std::move(paint), newCoverage,
835 viewMatrix, path,
836 devClipBounds, capLength, stencilSettings);
837 }
838
AAHairlineOp(GrProcessorSet * processorSet,const SkPMColor4f & color,uint8_t coverage,const SkMatrix & viewMatrix,const SkPath & path,SkIRect devClipBounds,SkScalar capLength,const GrUserStencilSettings * stencilSettings)839 AAHairlineOp(GrProcessorSet* processorSet,
840 const SkPMColor4f& color,
841 uint8_t coverage,
842 const SkMatrix& viewMatrix,
843 const SkPath& path,
844 SkIRect devClipBounds,
845 SkScalar capLength,
846 const GrUserStencilSettings* stencilSettings)
847 : INHERITED(ClassID())
848 , fHelper(processorSet, GrAAType::kCoverage, stencilSettings)
849 , fColor(color)
850 , fCoverage(coverage) {
851 fPaths.emplace_back(PathData{viewMatrix, path, devClipBounds, capLength});
852
853 this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes,
854 IsHairline::kYes);
855 }
856
name() const857 const char* name() const override { return "AAHairlineOp"; }
858
visitProxies(const GrVisitProxyFunc & func) const859 void visitProxies(const GrVisitProxyFunc& func) const override {
860
861 bool visited = false;
862 for (int i = 0; i < 3; ++i) {
863 if (fProgramInfos[i]) {
864 fProgramInfos[i]->visitFPProxies(func);
865 visited = true;
866 }
867 }
868
869 if (!visited) {
870 fHelper.visitProxies(func);
871 }
872 }
873
fixedFunctionFlags() const874 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
875
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)876 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
877 GrClampType clampType) override {
878 // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
879 return fHelper.finalizeProcessors(caps, clip, clampType,
880 GrProcessorAnalysisCoverage::kSingleChannel, &fColor,
881 nullptr);
882 }
883
884 enum class Program : uint8_t {
885 kNone = 0x0,
886 kLine = 0x1,
887 kQuad = 0x2,
888 kConic = 0x4,
889 };
890
891 private:
892 void makeLineProgramInfo(const GrCaps&, SkArenaAlloc*, const GrPipeline*,
893 const GrSurfaceProxyView& writeView,
894 bool usesMSAASurface,
895 const SkMatrix* geometryProcessorViewM,
896 const SkMatrix* geometryProcessorLocalM,
897 GrXferBarrierFlags renderPassXferBarriers,
898 GrLoadOp colorLoadOp);
899 void makeQuadProgramInfo(const GrCaps&, SkArenaAlloc*, const GrPipeline*,
900 const GrSurfaceProxyView& writeView,
901 bool usesMSAASurface,
902 const SkMatrix* geometryProcessorViewM,
903 const SkMatrix* geometryProcessorLocalM,
904 GrXferBarrierFlags renderPassXferBarriers,
905 GrLoadOp colorLoadOp);
906 void makeConicProgramInfo(const GrCaps&, SkArenaAlloc*, const GrPipeline*,
907 const GrSurfaceProxyView& writeView,
908 bool usesMSAASurface,
909 const SkMatrix* geometryProcessorViewM,
910 const SkMatrix* geometryProcessorLocalM,
911 GrXferBarrierFlags renderPassXferBarriers,
912 GrLoadOp colorLoadOp);
913
programInfo()914 GrProgramInfo* programInfo() override {
915 // This Op has 3 programInfos and implements its own onPrePrepareDraws so this entry point
916 // should really never be called.
917 SkASSERT(0);
918 return nullptr;
919 }
920
921 Program predictPrograms(const GrCaps*) const;
922
923 void onCreateProgramInfo(const GrCaps*,
924 SkArenaAlloc*,
925 const GrSurfaceProxyView& writeView,
926 bool usesMSAASurface,
927 GrAppliedClip&&,
928 const GrDstProxyView&,
929 GrXferBarrierFlags renderPassXferBarriers,
930 GrLoadOp colorLoadOp) override;
931
932 void onPrePrepareDraws(GrRecordingContext*,
933 const GrSurfaceProxyView& writeView,
934 GrAppliedClip*,
935 const GrDstProxyView&,
936 GrXferBarrierFlags renderPassXferBarriers,
937 GrLoadOp colorLoadOp) override;
938
939 void onPrepareDraws(GrMeshDrawTarget*) override;
940 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
941
onCombineIfPossible(GrOp * t,SkArenaAlloc *,const GrCaps & caps)942 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
943 AAHairlineOp* that = t->cast<AAHairlineOp>();
944
945 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
946 return CombineResult::kCannotCombine;
947 }
948
949 if (this->viewMatrix().hasPerspective() != that->viewMatrix().hasPerspective()) {
950 return CombineResult::kCannotCombine;
951 }
952
953 // We go to identity if we don't have perspective
954 if (this->viewMatrix().hasPerspective() &&
955 !SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) {
956 return CombineResult::kCannotCombine;
957 }
958
959 // TODO we can actually combine hairlines if they are the same color in a kind of bulk
960 // method but we haven't implemented this yet
961 // TODO investigate going to vertex color and coverage?
962 if (this->coverage() != that->coverage()) {
963 return CombineResult::kCannotCombine;
964 }
965
966 if (this->color() != that->color()) {
967 return CombineResult::kCannotCombine;
968 }
969
970 if (fHelper.usesLocalCoords() && !SkMatrixPriv::CheapEqual(this->viewMatrix(),
971 that->viewMatrix())) {
972 return CombineResult::kCannotCombine;
973 }
974
975 fPaths.push_back_n(that->fPaths.size(), that->fPaths.begin());
976 return CombineResult::kMerged;
977 }
978
979 #if defined(GPU_TEST_UTILS)
onDumpInfo() const980 SkString onDumpInfo() const override {
981 return SkStringPrintf("Color: 0x%08x Coverage: 0x%02x, Count: %d\n%s",
982 fColor.toBytes_RGBA(), fCoverage, fPaths.size(),
983 fHelper.dumpInfo().c_str());
984 }
985 #endif
986
color() const987 const SkPMColor4f& color() const { return fColor; }
coverage() const988 uint8_t coverage() const { return fCoverage; }
viewMatrix() const989 const SkMatrix& viewMatrix() const { return fPaths[0].fViewMatrix; }
990
991 struct PathData {
992 SkMatrix fViewMatrix;
993 SkPath fPath;
994 SkIRect fDevClipBounds;
995 SkScalar fCapLength;
996 };
997
998 STArray<1, PathData, true> fPaths;
999 Helper fHelper;
1000 SkPMColor4f fColor;
1001 uint8_t fCoverage;
1002
1003 Program fCharacterization = Program::kNone; // holds a mask of required programs
1004 GrSimpleMesh* fMeshes[3] = { nullptr };
1005 GrProgramInfo* fProgramInfos[3] = { nullptr };
1006
1007 using INHERITED = GrMeshDrawOp;
1008 };
1009
SK_MAKE_BITFIELD_CLASS_OPS(AAHairlineOp::Program)1010 SK_MAKE_BITFIELD_CLASS_OPS(AAHairlineOp::Program)
1011
1012 void AAHairlineOp::makeLineProgramInfo(const GrCaps& caps, SkArenaAlloc* arena,
1013 const GrPipeline* pipeline,
1014 const GrSurfaceProxyView& writeView,
1015 bool usesMSAASurface,
1016 const SkMatrix* geometryProcessorViewM,
1017 const SkMatrix* geometryProcessorLocalM,
1018 GrXferBarrierFlags renderPassXferBarriers,
1019 GrLoadOp colorLoadOp) {
1020 if (fProgramInfos[0]) {
1021 return;
1022 }
1023
1024 GrGeometryProcessor* lineGP;
1025 {
1026 using namespace GrDefaultGeoProcFactory;
1027
1028 Color color(this->color());
1029 LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type
1030 : LocalCoords::kUnused_Type);
1031 localCoords.fMatrix = geometryProcessorLocalM;
1032
1033 lineGP = GrDefaultGeoProcFactory::Make(arena,
1034 color,
1035 Coverage::kAttribute_Type,
1036 localCoords,
1037 *geometryProcessorViewM);
1038 SkASSERT(sizeof(LineVertex) == lineGP->vertexStride());
1039 }
1040
1041 fProgramInfos[0] = GrSimpleMeshDrawOpHelper::CreateProgramInfo(
1042 &caps, arena, pipeline, writeView, usesMSAASurface, lineGP, GrPrimitiveType::kTriangles,
1043 renderPassXferBarriers, colorLoadOp, fHelper.stencilSettings());
1044 }
1045
makeQuadProgramInfo(const GrCaps & caps,SkArenaAlloc * arena,const GrPipeline * pipeline,const GrSurfaceProxyView & writeView,bool usesMSAASurface,const SkMatrix * geometryProcessorViewM,const SkMatrix * geometryProcessorLocalM,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)1046 void AAHairlineOp::makeQuadProgramInfo(const GrCaps& caps, SkArenaAlloc* arena,
1047 const GrPipeline* pipeline,
1048 const GrSurfaceProxyView& writeView,
1049 bool usesMSAASurface,
1050 const SkMatrix* geometryProcessorViewM,
1051 const SkMatrix* geometryProcessorLocalM,
1052 GrXferBarrierFlags renderPassXferBarriers,
1053 GrLoadOp colorLoadOp) {
1054 if (fProgramInfos[1]) {
1055 return;
1056 }
1057
1058 GrGeometryProcessor* quadGP = GrQuadEffect::Make(arena,
1059 this->color(),
1060 *geometryProcessorViewM,
1061 caps,
1062 *geometryProcessorLocalM,
1063 fHelper.usesLocalCoords(),
1064 this->coverage());
1065 SkASSERT(sizeof(BezierVertex) == quadGP->vertexStride());
1066
1067 fProgramInfos[1] = GrSimpleMeshDrawOpHelper::CreateProgramInfo(
1068 &caps, arena, pipeline, writeView, usesMSAASurface, quadGP, GrPrimitiveType::kTriangles,
1069 renderPassXferBarriers, colorLoadOp, fHelper.stencilSettings());
1070 }
1071
makeConicProgramInfo(const GrCaps & caps,SkArenaAlloc * arena,const GrPipeline * pipeline,const GrSurfaceProxyView & writeView,bool usesMSAASurface,const SkMatrix * geometryProcessorViewM,const SkMatrix * geometryProcessorLocalM,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)1072 void AAHairlineOp::makeConicProgramInfo(const GrCaps& caps, SkArenaAlloc* arena,
1073 const GrPipeline* pipeline,
1074 const GrSurfaceProxyView& writeView,
1075 bool usesMSAASurface,
1076 const SkMatrix* geometryProcessorViewM,
1077 const SkMatrix* geometryProcessorLocalM,
1078 GrXferBarrierFlags renderPassXferBarriers,
1079 GrLoadOp colorLoadOp) {
1080 if (fProgramInfos[2]) {
1081 return;
1082 }
1083
1084 GrGeometryProcessor* conicGP = GrConicEffect::Make(arena,
1085 this->color(),
1086 *geometryProcessorViewM,
1087 caps,
1088 *geometryProcessorLocalM,
1089 fHelper.usesLocalCoords(),
1090 this->coverage());
1091 SkASSERT(sizeof(BezierVertex) == conicGP->vertexStride());
1092
1093 fProgramInfos[2] = GrSimpleMeshDrawOpHelper::CreateProgramInfo(
1094 &caps, arena, pipeline, writeView, usesMSAASurface, conicGP,
1095 GrPrimitiveType::kTriangles, renderPassXferBarriers, colorLoadOp,
1096 fHelper.stencilSettings());
1097 }
1098
predictPrograms(const GrCaps * caps) const1099 AAHairlineOp::Program AAHairlineOp::predictPrograms(const GrCaps* caps) const {
1100 bool convertConicsToQuads = !caps->shaderCaps()->fFloatIs32Bits;
1101
1102 // When predicting the programs we always include the lineProgram bc it is used as a fallback
1103 // for quads and conics. In non-DDL mode there are cases where it sometimes isn't needed for a
1104 // given path.
1105 Program neededPrograms = Program::kLine;
1106
1107 for (int i = 0; i < fPaths.size(); i++) {
1108 uint32_t mask = fPaths[i].fPath.getSegmentMasks();
1109
1110 if (mask & (SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask)) {
1111 neededPrograms |= Program::kQuad;
1112 }
1113 if (mask & SkPath::kConic_SegmentMask) {
1114 if (convertConicsToQuads) {
1115 neededPrograms |= Program::kQuad;
1116 } else {
1117 neededPrograms |= Program::kConic;
1118 }
1119 }
1120 }
1121
1122 return neededPrograms;
1123 }
1124
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && appliedClip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)1125 void AAHairlineOp::onCreateProgramInfo(const GrCaps* caps,
1126 SkArenaAlloc* arena,
1127 const GrSurfaceProxyView& writeView,
1128 bool usesMSAASurface,
1129 GrAppliedClip&& appliedClip,
1130 const GrDstProxyView& dstProxyView,
1131 GrXferBarrierFlags renderPassXferBarriers,
1132 GrLoadOp colorLoadOp) {
1133 // Setup the viewmatrix and localmatrix for the GrGeometryProcessor.
1134 SkMatrix invert;
1135 if (!this->viewMatrix().invert(&invert)) {
1136 return;
1137 }
1138
1139 // we will transform to identity space if the viewmatrix does not have perspective
1140 bool hasPerspective = this->viewMatrix().hasPerspective();
1141 const SkMatrix* geometryProcessorViewM = &SkMatrix::I();
1142 const SkMatrix* geometryProcessorLocalM = &invert;
1143 if (hasPerspective) {
1144 geometryProcessorViewM = &this->viewMatrix();
1145 geometryProcessorLocalM = &SkMatrix::I();
1146 }
1147
1148 auto pipeline = fHelper.createPipeline(caps, arena, writeView.swizzle(),
1149 std::move(appliedClip), dstProxyView);
1150
1151 if (fCharacterization & Program::kLine) {
1152 this->makeLineProgramInfo(*caps, arena, pipeline, writeView, usesMSAASurface,
1153 geometryProcessorViewM, geometryProcessorLocalM,
1154 renderPassXferBarriers, colorLoadOp);
1155 }
1156 if (fCharacterization & Program::kQuad) {
1157 this->makeQuadProgramInfo(*caps, arena, pipeline, writeView, usesMSAASurface,
1158 geometryProcessorViewM, geometryProcessorLocalM,
1159 renderPassXferBarriers, colorLoadOp);
1160 }
1161 if (fCharacterization & Program::kConic) {
1162 this->makeConicProgramInfo(*caps, arena, pipeline, writeView, usesMSAASurface,
1163 geometryProcessorViewM, geometryProcessorLocalM,
1164 renderPassXferBarriers, colorLoadOp);
1165
1166 }
1167 }
1168
onPrePrepareDraws(GrRecordingContext * context,const GrSurfaceProxyView & writeView,GrAppliedClip * clip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)1169 void AAHairlineOp::onPrePrepareDraws(GrRecordingContext* context,
1170 const GrSurfaceProxyView& writeView,
1171 GrAppliedClip* clip,
1172 const GrDstProxyView& dstProxyView,
1173 GrXferBarrierFlags renderPassXferBarriers,
1174 GrLoadOp colorLoadOp) {
1175 SkArenaAlloc* arena = context->priv().recordTimeAllocator();
1176 const GrCaps* caps = context->priv().caps();
1177
1178 // http://skbug.com/12201 -- DDL does not yet support DMSAA.
1179 bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
1180
1181 // This is equivalent to a GrOpFlushState::detachAppliedClip
1182 GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip::Disabled();
1183
1184 // Conservatively predict which programs will be required
1185 fCharacterization = this->predictPrograms(caps);
1186
1187 this->createProgramInfo(caps, arena, writeView, usesMSAASurface, std::move(appliedClip),
1188 dstProxyView, renderPassXferBarriers, colorLoadOp);
1189
1190 context->priv().recordProgramInfo(fProgramInfos[0]);
1191 context->priv().recordProgramInfo(fProgramInfos[1]);
1192 context->priv().recordProgramInfo(fProgramInfos[2]);
1193 }
1194
onPrepareDraws(GrMeshDrawTarget * target)1195 void AAHairlineOp::onPrepareDraws(GrMeshDrawTarget* target) {
1196 // Setup the viewmatrix and localmatrix for the GrGeometryProcessor.
1197 SkMatrix invert;
1198 if (!this->viewMatrix().invert(&invert)) {
1199 return;
1200 }
1201
1202 // we will transform to identity space if the viewmatrix does not have perspective
1203 const SkMatrix* toDevice = nullptr;
1204 const SkMatrix* toSrc = nullptr;
1205 if (this->viewMatrix().hasPerspective()) {
1206 toDevice = &this->viewMatrix();
1207 toSrc = &invert;
1208 }
1209
1210 SkDEBUGCODE(Program predictedPrograms = this->predictPrograms(&target->caps()));
1211 Program actualPrograms = Program::kNone;
1212
1213 // This is hand inlined for maximum performance.
1214 PREALLOC_PTARRAY(128) lines;
1215 PREALLOC_PTARRAY(128) quads;
1216 PREALLOC_PTARRAY(128) conics;
1217 IntArray qSubdivs;
1218 FloatArray cWeights;
1219 int quadCount = 0;
1220
1221 int instanceCount = fPaths.size();
1222 bool convertConicsToQuads = !target->caps().shaderCaps()->fFloatIs32Bits;
1223 SkSafeMath safeMath;
1224 for (int i = 0; i < instanceCount && safeMath.ok(); i++) {
1225 const PathData& args = fPaths[i];
1226 quadCount = safeMath.addInt(quadCount,
1227 gather_lines_and_quads(args.fPath,
1228 args.fViewMatrix,
1229 args.fDevClipBounds,
1230 args.fCapLength,
1231 convertConicsToQuads,
1232 &lines,
1233 &quads,
1234 &conics,
1235 &qSubdivs,
1236 &cWeights));
1237 }
1238
1239 int lineCount = lines.size() / 2;
1240 int conicCount = conics.size() / 3;
1241 int quadAndConicCount = safeMath.addInt(conicCount, quadCount);
1242 if (!safeMath.ok()) {
1243 return;
1244 }
1245
1246 static constexpr int kMaxLines = SK_MaxS32 / kLineSegNumVertices;
1247 static constexpr int kMaxQuadsAndConics = SK_MaxS32 / kQuadNumVertices;
1248 if (lineCount > kMaxLines || quadAndConicCount > kMaxQuadsAndConics) {
1249 return;
1250 }
1251
1252 // do lines first
1253 if (lineCount) {
1254 SkASSERT(predictedPrograms & Program::kLine);
1255 actualPrograms |= Program::kLine;
1256
1257 sk_sp<const GrBuffer> linesIndexBuffer = get_lines_index_buffer(target->resourceProvider());
1258
1259 GrMeshDrawOp::PatternHelper helper(target, GrPrimitiveType::kTriangles, sizeof(LineVertex),
1260 std::move(linesIndexBuffer), kLineSegNumVertices,
1261 kIdxsPerLineSeg, lineCount, kLineSegsNumInIdxBuffer);
1262
1263 LineVertex* verts = reinterpret_cast<LineVertex*>(helper.vertices());
1264 if (!verts) {
1265 SkDebugf("Could not allocate vertices\n");
1266 return;
1267 }
1268
1269 for (int i = 0; i < lineCount; ++i) {
1270 add_line(&lines[2*i], toSrc, this->coverage(), &verts);
1271 }
1272
1273 fMeshes[0] = helper.mesh();
1274 }
1275
1276 if (quadCount || conicCount) {
1277 sk_sp<const GrBuffer> vertexBuffer;
1278 int firstVertex;
1279
1280 sk_sp<const GrBuffer> quadsIndexBuffer = get_quads_index_buffer(target->resourceProvider());
1281
1282 int vertexCount = kQuadNumVertices * quadAndConicCount;
1283 void* vertices = target->makeVertexSpace(sizeof(BezierVertex), vertexCount, &vertexBuffer,
1284 &firstVertex);
1285
1286 if (!vertices || !quadsIndexBuffer) {
1287 SkDebugf("Could not allocate vertices\n");
1288 return;
1289 }
1290
1291 // Setup vertices
1292 BezierVertex* bezVerts = reinterpret_cast<BezierVertex*>(vertices);
1293
1294 int unsubdivQuadCnt = quads.size() / 3;
1295 for (int i = 0; i < unsubdivQuadCnt; ++i) {
1296 SkASSERT(qSubdivs[i] >= 0);
1297 if (!quads[3*i].isFinite() || !quads[3*i+1].isFinite() || !quads[3*i+2].isFinite()) {
1298 return;
1299 }
1300 add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &bezVerts);
1301 }
1302
1303 // Start Conics
1304 for (int i = 0; i < conicCount; ++i) {
1305 add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &bezVerts);
1306 }
1307
1308 if (quadCount > 0) {
1309 SkASSERT(predictedPrograms & Program::kQuad);
1310 actualPrograms |= Program::kQuad;
1311
1312 fMeshes[1] = target->allocMesh();
1313 fMeshes[1]->setIndexedPatterned(quadsIndexBuffer, kIdxsPerQuad, quadCount,
1314 kQuadsNumInIdxBuffer, vertexBuffer, kQuadNumVertices,
1315 firstVertex);
1316 firstVertex += quadCount * kQuadNumVertices;
1317 }
1318
1319 if (conicCount > 0) {
1320 SkASSERT(predictedPrograms & Program::kConic);
1321 actualPrograms |= Program::kConic;
1322
1323 fMeshes[2] = target->allocMesh();
1324 fMeshes[2]->setIndexedPatterned(std::move(quadsIndexBuffer), kIdxsPerQuad, conicCount,
1325 kQuadsNumInIdxBuffer, std::move(vertexBuffer),
1326 kQuadNumVertices, firstVertex);
1327 }
1328 }
1329
1330 // In DDL mode this will replace the predicted program requirements with the actual ones.
1331 // However, we will already have surfaced the predicted programs to the DDL.
1332 fCharacterization = actualPrograms;
1333 }
1334
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)1335 void AAHairlineOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
1336 this->createProgramInfo(flushState);
1337
1338 for (int i = 0; i < 3; ++i) {
1339 if (fProgramInfos[i] && fMeshes[i]) {
1340 flushState->bindPipelineAndScissorClip(*fProgramInfos[i], chainBounds);
1341 flushState->bindTextures(fProgramInfos[i]->geomProc(), nullptr,
1342 fProgramInfos[i]->pipeline());
1343 flushState->drawMesh(*fMeshes[i]);
1344 }
1345 }
1346 }
1347
1348 } // anonymous namespace
1349
1350 ///////////////////////////////////////////////////////////////////////////////////////////////////
1351
1352 #if defined(GPU_TEST_UTILS)
1353
GR_DRAW_OP_TEST_DEFINE(AAHairlineOp)1354 GR_DRAW_OP_TEST_DEFINE(AAHairlineOp) {
1355 SkMatrix viewMatrix = GrTest::TestMatrix(random);
1356 const SkPath& path = GrTest::TestPath(random);
1357 SkIRect devClipBounds;
1358 devClipBounds.setEmpty();
1359 return AAHairlineOp::Make(context, std::move(paint), viewMatrix, path,
1360 GrStyle::SimpleHairline(), devClipBounds,
1361 GrGetRandomStencil(random, context));
1362 }
1363
1364 #endif
1365
1366 ///////////////////////////////////////////////////////////////////////////////////////////////////
1367
1368 namespace skgpu::ganesh {
1369
onCanDrawPath(const CanDrawPathArgs & args) const1370 PathRenderer::CanDrawPath AAHairLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
1371 if (GrAAType::kCoverage != args.fAAType) {
1372 return CanDrawPath::kNo;
1373 }
1374
1375 if (!GrIsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr)) {
1376 return CanDrawPath::kNo;
1377 }
1378
1379 // We don't currently handle dashing in this class though perhaps we should.
1380 if (args.fShape->style().pathEffect()) {
1381 return CanDrawPath::kNo;
1382 }
1383
1384 if (SkPath::kLine_SegmentMask == args.fShape->segmentMask() ||
1385 args.fCaps->shaderCaps()->fShaderDerivativeSupport) {
1386 return CanDrawPath::kYes;
1387 }
1388
1389 return CanDrawPath::kNo;
1390 }
1391
1392
onDrawPath(const DrawPathArgs & args)1393 bool AAHairLinePathRenderer::onDrawPath(const DrawPathArgs& args) {
1394 GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
1395 "AAHairlinePathRenderer::onDrawPath");
1396 SkASSERT(args.fSurfaceDrawContext->numSamples() <= 1);
1397
1398 SkPath path;
1399 args.fShape->asPath(&path);
1400 GrOp::Owner op =
1401 AAHairlineOp::Make(args.fContext, std::move(args.fPaint), *args.fViewMatrix, path,
1402 args.fShape->style(), *args.fClipConservativeBounds,
1403 args.fUserStencilSettings);
1404 args.fSurfaceDrawContext->addDrawOp(args.fClip, std::move(op));
1405 return true;
1406 }
1407
1408 } // namespace skgpu::ganesh
1409