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