• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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