OLD | NEW |
| (Empty) |
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 | |
8 #include "GrAAHairLinePathRenderer.h" | |
9 | |
10 #include "GrBatchFlushState.h" | |
11 #include "GrBatchTest.h" | |
12 #include "GrCaps.h" | |
13 #include "GrContext.h" | |
14 #include "GrDefaultGeoProcFactory.h" | |
15 #include "GrIndexBuffer.h" | |
16 #include "GrPathUtils.h" | |
17 #include "GrPipelineBuilder.h" | |
18 #include "GrProcessor.h" | |
19 #include "GrResourceProvider.h" | |
20 #include "GrVertexBuffer.h" | |
21 #include "SkGeometry.h" | |
22 #include "SkStroke.h" | |
23 #include "SkTemplates.h" | |
24 | |
25 #include "batches/GrVertexBatch.h" | |
26 | |
27 #include "effects/GrBezierEffect.h" | |
28 | |
29 #define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true> | |
30 | |
31 // quadratics are rendered as 5-sided polys in order to bound the | |
32 // AA stroke around the center-curve. See comments in push_quad_index_buffer and | |
33 // bloat_quad. Quadratics and conics share an index buffer | |
34 | |
35 // lines are rendered as: | |
36 // *______________* | |
37 // |\ -_______ /| | |
38 // | \ \ / | | |
39 // | *--------* | | |
40 // | / ______/ \ | | |
41 // */_-__________\* | |
42 // For: 6 vertices and 18 indices (for 6 triangles) | |
43 | |
44 // Each quadratic is rendered as a five sided polygon. This poly bounds | |
45 // the quadratic's bounding triangle but has been expanded so that the | |
46 // 1-pixel wide area around the curve is inside the poly. | |
47 // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1 | |
48 // that is rendered would look like this: | |
49 // b0 | |
50 // b | |
51 // | |
52 // a0 c0 | |
53 // a c | |
54 // a1 c1 | |
55 // Each is drawn as three triangles ((a0,a1,b0), (b0,c1,c0), (a1,c1,b0)) | |
56 // specified by these 9 indices: | |
57 static const uint16_t kQuadIdxBufPattern[] = { | |
58 0, 1, 2, | |
59 2, 4, 3, | |
60 1, 4, 2 | |
61 }; | |
62 | |
63 static const int kIdxsPerQuad = SK_ARRAY_COUNT(kQuadIdxBufPattern); | |
64 static const int kQuadNumVertices = 5; | |
65 static const int kQuadsNumInIdxBuffer = 256; | |
66 GR_DECLARE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey); | |
67 | |
68 static const GrIndexBuffer* ref_quads_index_buffer(GrResourceProvider* resourceP
rovider) { | |
69 GR_DEFINE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey); | |
70 return resourceProvider->findOrCreateInstancedIndexBuffer( | |
71 kQuadIdxBufPattern, kIdxsPerQuad, kQuadsNumInIdxBuffer, kQuadNumVertices
, | |
72 gQuadsIndexBufferKey); | |
73 } | |
74 | |
75 | |
76 // Each line segment is rendered as two quads and two triangles. | |
77 // p0 and p1 have alpha = 1 while all other points have alpha = 0. | |
78 // The four external points are offset 1 pixel perpendicular to the | |
79 // line and half a pixel parallel to the line. | |
80 // | |
81 // p4 p5 | |
82 // p0 p1 | |
83 // p2 p3 | |
84 // | |
85 // Each is drawn as six triangles specified by these 18 indices: | |
86 | |
87 static const uint16_t kLineSegIdxBufPattern[] = { | |
88 0, 1, 3, | |
89 0, 3, 2, | |
90 0, 4, 5, | |
91 0, 5, 1, | |
92 0, 2, 4, | |
93 1, 5, 3 | |
94 }; | |
95 | |
96 static const int kIdxsPerLineSeg = SK_ARRAY_COUNT(kLineSegIdxBufPattern); | |
97 static const int kLineSegNumVertices = 6; | |
98 static const int kLineSegsNumInIdxBuffer = 256; | |
99 | |
100 GR_DECLARE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey); | |
101 | |
102 static const GrIndexBuffer* ref_lines_index_buffer(GrResourceProvider* resourceP
rovider) { | |
103 GR_DEFINE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey); | |
104 return resourceProvider->findOrCreateInstancedIndexBuffer( | |
105 kLineSegIdxBufPattern, kIdxsPerLineSeg, kLineSegsNumInIdxBuffer, kLineS
egNumVertices, | |
106 gLinesIndexBufferKey); | |
107 } | |
108 | |
109 // Takes 178th time of logf on Z600 / VC2010 | |
110 static int get_float_exp(float x) { | |
111 GR_STATIC_ASSERT(sizeof(int) == sizeof(float)); | |
112 #ifdef SK_DEBUG | |
113 static bool tested; | |
114 if (!tested) { | |
115 tested = true; | |
116 SkASSERT(get_float_exp(0.25f) == -2); | |
117 SkASSERT(get_float_exp(0.3f) == -2); | |
118 SkASSERT(get_float_exp(0.5f) == -1); | |
119 SkASSERT(get_float_exp(1.f) == 0); | |
120 SkASSERT(get_float_exp(2.f) == 1); | |
121 SkASSERT(get_float_exp(2.5f) == 1); | |
122 SkASSERT(get_float_exp(8.f) == 3); | |
123 SkASSERT(get_float_exp(100.f) == 6); | |
124 SkASSERT(get_float_exp(1000.f) == 9); | |
125 SkASSERT(get_float_exp(1024.f) == 10); | |
126 SkASSERT(get_float_exp(3000000.f) == 21); | |
127 } | |
128 #endif | |
129 const int* iptr = (const int*)&x; | |
130 return (((*iptr) & 0x7f800000) >> 23) - 127; | |
131 } | |
132 | |
133 // Uses the max curvature function for quads to estimate | |
134 // where to chop the conic. If the max curvature is not | |
135 // found along the curve segment it will return 1 and | |
136 // dst[0] is the original conic. If it returns 2 the dst[0] | |
137 // and dst[1] are the two new conics. | |
138 static int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weig
ht) { | |
139 SkScalar t = SkFindQuadMaxCurvature(src); | |
140 if (t == 0) { | |
141 if (dst) { | |
142 dst[0].set(src, weight); | |
143 } | |
144 return 1; | |
145 } else { | |
146 if (dst) { | |
147 SkConic conic; | |
148 conic.set(src, weight); | |
149 conic.chopAt(t, dst); | |
150 } | |
151 return 2; | |
152 } | |
153 } | |
154 | |
155 // Calls split_conic on the entire conic and then once more on each subsection. | |
156 // Most cases will result in either 1 conic (chop point is not within t range) | |
157 // or 3 points (split once and then one subsection is split again). | |
158 static int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weigh
t) { | |
159 SkConic dstTemp[2]; | |
160 int conicCnt = split_conic(src, dstTemp, weight); | |
161 if (2 == conicCnt) { | |
162 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW); | |
163 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dst
Temp[1].fW); | |
164 } else { | |
165 dst[0] = dstTemp[0]; | |
166 } | |
167 return conicCnt; | |
168 } | |
169 | |
170 // returns 0 if quad/conic is degen or close to it | |
171 // in this case approx the path with lines | |
172 // otherwise returns 1 | |
173 static int is_degen_quad_or_conic(const SkPoint p[3], SkScalar* dsqd) { | |
174 static const SkScalar gDegenerateToLineTol = GrPathUtils::kDefaultTolerance; | |
175 static const SkScalar gDegenerateToLineTolSqd = | |
176 SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol); | |
177 | |
178 if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd || | |
179 p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) { | |
180 return 1; | |
181 } | |
182 | |
183 *dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]); | |
184 if (*dsqd < gDegenerateToLineTolSqd) { | |
185 return 1; | |
186 } | |
187 | |
188 if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) { | |
189 return 1; | |
190 } | |
191 return 0; | |
192 } | |
193 | |
194 static int is_degen_quad_or_conic(const SkPoint p[3]) { | |
195 SkScalar dsqd; | |
196 return is_degen_quad_or_conic(p, &dsqd); | |
197 } | |
198 | |
199 // we subdivide the quads to avoid huge overfill | |
200 // if it returns -1 then should be drawn as lines | |
201 static int num_quad_subdivs(const SkPoint p[3]) { | |
202 SkScalar dsqd; | |
203 if (is_degen_quad_or_conic(p, &dsqd)) { | |
204 return -1; | |
205 } | |
206 | |
207 // tolerance of triangle height in pixels | |
208 // tuned on windows Quadro FX 380 / Z600 | |
209 // trade off of fill vs cpu time on verts | |
210 // maybe different when do this using gpu (geo or tess shaders) | |
211 static const SkScalar gSubdivTol = 175 * SK_Scalar1; | |
212 | |
213 if (dsqd <= SkScalarMul(gSubdivTol, gSubdivTol)) { | |
214 return 0; | |
215 } else { | |
216 static const int kMaxSub = 4; | |
217 // subdividing the quad reduces d by 4. so we want x = log4(d/tol) | |
218 // = log4(d*d/tol*tol)/2 | |
219 // = log2(d*d/tol*tol) | |
220 | |
221 // +1 since we're ignoring the mantissa contribution. | |
222 int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1; | |
223 log = SkTMin(SkTMax(0, log), kMaxSub); | |
224 return log; | |
225 } | |
226 } | |
227 | |
228 /** | |
229 * Generates the lines and quads to be rendered. Lines are always recorded in | |
230 * device space. We will do a device space bloat to account for the 1pixel | |
231 * thickness. | |
232 * Quads are recorded in device space unless m contains | |
233 * perspective, then in they are in src space. We do this because we will | |
234 * subdivide large quads to reduce over-fill. This subdivision has to be | |
235 * performed before applying the perspective matrix. | |
236 */ | |
237 static int gather_lines_and_quads(const SkPath& path, | |
238 const SkMatrix& m, | |
239 const SkIRect& devClipBounds, | |
240 GrAAHairLinePathRenderer::PtArray* lines, | |
241 GrAAHairLinePathRenderer::PtArray* quads, | |
242 GrAAHairLinePathRenderer::PtArray* conics, | |
243 GrAAHairLinePathRenderer::IntArray* quadSubdiv
Cnts, | |
244 GrAAHairLinePathRenderer::FloatArray* conicWei
ghts) { | |
245 SkPath::Iter iter(path, false); | |
246 | |
247 int totalQuadCount = 0; | |
248 SkRect bounds; | |
249 SkIRect ibounds; | |
250 | |
251 bool persp = m.hasPerspective(); | |
252 | |
253 for (;;) { | |
254 SkPoint pathPts[4]; | |
255 SkPoint devPts[4]; | |
256 SkPath::Verb verb = iter.next(pathPts); | |
257 switch (verb) { | |
258 case SkPath::kConic_Verb: { | |
259 SkConic dst[4]; | |
260 // We chop the conics to create tighter clipping to hide error | |
261 // that appears near max curvature of very thin conics. Thin | |
262 // hyperbolas with high weight still show error. | |
263 int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); | |
264 for (int i = 0; i < conicCnt; ++i) { | |
265 SkPoint* chopPnts = dst[i].fPts; | |
266 m.mapPoints(devPts, chopPnts, 3); | |
267 bounds.setBounds(devPts, 3); | |
268 bounds.outset(SK_Scalar1, SK_Scalar1); | |
269 bounds.roundOut(&ibounds); | |
270 if (SkIRect::Intersects(devClipBounds, ibounds)) { | |
271 if (is_degen_quad_or_conic(devPts)) { | |
272 SkPoint* pts = lines->push_back_n(4); | |
273 pts[0] = devPts[0]; | |
274 pts[1] = devPts[1]; | |
275 pts[2] = devPts[1]; | |
276 pts[3] = devPts[2]; | |
277 } else { | |
278 // when in perspective keep conics in src space | |
279 SkPoint* cPts = persp ? chopPnts : devPts; | |
280 SkPoint* pts = conics->push_back_n(3); | |
281 pts[0] = cPts[0]; | |
282 pts[1] = cPts[1]; | |
283 pts[2] = cPts[2]; | |
284 conicWeights->push_back() = dst[i].fW; | |
285 } | |
286 } | |
287 } | |
288 break; | |
289 } | |
290 case SkPath::kMove_Verb: | |
291 break; | |
292 case SkPath::kLine_Verb: | |
293 m.mapPoints(devPts, pathPts, 2); | |
294 bounds.setBounds(devPts, 2); | |
295 bounds.outset(SK_Scalar1, SK_Scalar1); | |
296 bounds.roundOut(&ibounds); | |
297 if (SkIRect::Intersects(devClipBounds, ibounds)) { | |
298 SkPoint* pts = lines->push_back_n(2); | |
299 pts[0] = devPts[0]; | |
300 pts[1] = devPts[1]; | |
301 } | |
302 break; | |
303 case SkPath::kQuad_Verb: { | |
304 SkPoint choppedPts[5]; | |
305 // Chopping the quad helps when the quad is either degenerate or
nearly degenerate. | |
306 // When it is degenerate it allows the approximation with lines
to work since the | |
307 // chop point (if there is one) will be at the parabola's vertex
. In the nearly | |
308 // degenerate the QuadUVMatrix computed for the points is almost
singular which | |
309 // can cause rendering artifacts. | |
310 int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); | |
311 for (int i = 0; i < n; ++i) { | |
312 SkPoint* quadPts = choppedPts + i * 2; | |
313 m.mapPoints(devPts, quadPts, 3); | |
314 bounds.setBounds(devPts, 3); | |
315 bounds.outset(SK_Scalar1, SK_Scalar1); | |
316 bounds.roundOut(&ibounds); | |
317 | |
318 if (SkIRect::Intersects(devClipBounds, ibounds)) { | |
319 int subdiv = num_quad_subdivs(devPts); | |
320 SkASSERT(subdiv >= -1); | |
321 if (-1 == subdiv) { | |
322 SkPoint* pts = lines->push_back_n(4); | |
323 pts[0] = devPts[0]; | |
324 pts[1] = devPts[1]; | |
325 pts[2] = devPts[1]; | |
326 pts[3] = devPts[2]; | |
327 } else { | |
328 // when in perspective keep quads in src space | |
329 SkPoint* qPts = persp ? quadPts : devPts; | |
330 SkPoint* pts = quads->push_back_n(3); | |
331 pts[0] = qPts[0]; | |
332 pts[1] = qPts[1]; | |
333 pts[2] = qPts[2]; | |
334 quadSubdivCnts->push_back() = subdiv; | |
335 totalQuadCount += 1 << subdiv; | |
336 } | |
337 } | |
338 } | |
339 break; | |
340 } | |
341 case SkPath::kCubic_Verb: | |
342 m.mapPoints(devPts, pathPts, 4); | |
343 bounds.setBounds(devPts, 4); | |
344 bounds.outset(SK_Scalar1, SK_Scalar1); | |
345 bounds.roundOut(&ibounds); | |
346 if (SkIRect::Intersects(devClipBounds, ibounds)) { | |
347 PREALLOC_PTARRAY(32) q; | |
348 // we don't need a direction if we aren't constraining the s
ubdivision | |
349 const SkPathPriv::FirstDirection kDummyDir = SkPathPriv::kCC
W_FirstDirection; | |
350 // We convert cubics to quadratics (for now). | |
351 // In perspective have to do conversion in src space. | |
352 if (persp) { | |
353 SkScalar tolScale = | |
354 GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, | |
355 path.getBounds()); | |
356 GrPathUtils::convertCubicToQuads(pathPts, tolScale, fals
e, kDummyDir, &q); | |
357 } else { | |
358 GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, fal
se, kDummyDir, &q); | |
359 } | |
360 for (int i = 0; i < q.count(); i += 3) { | |
361 SkPoint* qInDevSpace; | |
362 // bounds has to be calculated in device space, but q is | |
363 // in src space when there is perspective. | |
364 if (persp) { | |
365 m.mapPoints(devPts, &q[i], 3); | |
366 bounds.setBounds(devPts, 3); | |
367 qInDevSpace = devPts; | |
368 } else { | |
369 bounds.setBounds(&q[i], 3); | |
370 qInDevSpace = &q[i]; | |
371 } | |
372 bounds.outset(SK_Scalar1, SK_Scalar1); | |
373 bounds.roundOut(&ibounds); | |
374 if (SkIRect::Intersects(devClipBounds, ibounds)) { | |
375 int subdiv = num_quad_subdivs(qInDevSpace); | |
376 SkASSERT(subdiv >= -1); | |
377 if (-1 == subdiv) { | |
378 SkPoint* pts = lines->push_back_n(4); | |
379 // lines should always be in device coords | |
380 pts[0] = qInDevSpace[0]; | |
381 pts[1] = qInDevSpace[1]; | |
382 pts[2] = qInDevSpace[1]; | |
383 pts[3] = qInDevSpace[2]; | |
384 } else { | |
385 SkPoint* pts = quads->push_back_n(3); | |
386 // q is already in src space when there is no | |
387 // perspective and dev coords otherwise. | |
388 pts[0] = q[0 + i]; | |
389 pts[1] = q[1 + i]; | |
390 pts[2] = q[2 + i]; | |
391 quadSubdivCnts->push_back() = subdiv; | |
392 totalQuadCount += 1 << subdiv; | |
393 } | |
394 } | |
395 } | |
396 } | |
397 break; | |
398 case SkPath::kClose_Verb: | |
399 break; | |
400 case SkPath::kDone_Verb: | |
401 return totalQuadCount; | |
402 } | |
403 } | |
404 } | |
405 | |
406 struct LineVertex { | |
407 SkPoint fPos; | |
408 float fCoverage; | |
409 }; | |
410 | |
411 struct BezierVertex { | |
412 SkPoint fPos; | |
413 union { | |
414 struct { | |
415 SkScalar fK; | |
416 SkScalar fL; | |
417 SkScalar fM; | |
418 } fConic; | |
419 SkVector fQuadCoord; | |
420 struct { | |
421 SkScalar fBogus[4]; | |
422 }; | |
423 }; | |
424 }; | |
425 | |
426 GR_STATIC_ASSERT(sizeof(BezierVertex) == 3 * sizeof(SkPoint)); | |
427 | |
428 static void intersect_lines(const SkPoint& ptA, const SkVector& normA, | |
429 const SkPoint& ptB, const SkVector& normB, | |
430 SkPoint* result) { | |
431 | |
432 SkScalar lineAW = -normA.dot(ptA); | |
433 SkScalar lineBW = -normB.dot(ptB); | |
434 | |
435 SkScalar wInv = SkScalarMul(normA.fX, normB.fY) - | |
436 SkScalarMul(normA.fY, normB.fX); | |
437 wInv = SkScalarInvert(wInv); | |
438 | |
439 result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY); | |
440 result->fX = SkScalarMul(result->fX, wInv); | |
441 | |
442 result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW); | |
443 result->fY = SkScalarMul(result->fY, wInv); | |
444 } | |
445 | |
446 static void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kQuadNumVertic
es]) { | |
447 // this should be in the src space, not dev coords, when we have perspective | |
448 GrPathUtils::QuadUVMatrix DevToUV(qpts); | |
449 DevToUV.apply<kQuadNumVertices, sizeof(BezierVertex), sizeof(SkPoint)>(verts
); | |
450 } | |
451 | |
452 static void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice, | |
453 const SkMatrix* toSrc, BezierVertex verts[kQuadNumVertice
s]) { | |
454 SkASSERT(!toDevice == !toSrc); | |
455 // original quad is specified by tri a,b,c | |
456 SkPoint a = qpts[0]; | |
457 SkPoint b = qpts[1]; | |
458 SkPoint c = qpts[2]; | |
459 | |
460 if (toDevice) { | |
461 toDevice->mapPoints(&a, 1); | |
462 toDevice->mapPoints(&b, 1); | |
463 toDevice->mapPoints(&c, 1); | |
464 } | |
465 // make a new poly where we replace a and c by a 1-pixel wide edges orthog | |
466 // to edges ab and bc: | |
467 // | |
468 // before | after | |
469 // | b0 | |
470 // b | | |
471 // | | |
472 // | a0 c0 | |
473 // a c | a1 c1 | |
474 // | |
475 // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c, | |
476 // respectively. | |
477 BezierVertex& a0 = verts[0]; | |
478 BezierVertex& a1 = verts[1]; | |
479 BezierVertex& b0 = verts[2]; | |
480 BezierVertex& c0 = verts[3]; | |
481 BezierVertex& c1 = verts[4]; | |
482 | |
483 SkVector ab = b; | |
484 ab -= a; | |
485 SkVector ac = c; | |
486 ac -= a; | |
487 SkVector cb = b; | |
488 cb -= c; | |
489 | |
490 // We should have already handled degenerates | |
491 SkASSERT(ab.length() > 0 && cb.length() > 0); | |
492 | |
493 ab.normalize(); | |
494 SkVector abN; | |
495 abN.setOrthog(ab, SkVector::kLeft_Side); | |
496 if (abN.dot(ac) > 0) { | |
497 abN.negate(); | |
498 } | |
499 | |
500 cb.normalize(); | |
501 SkVector cbN; | |
502 cbN.setOrthog(cb, SkVector::kLeft_Side); | |
503 if (cbN.dot(ac) < 0) { | |
504 cbN.negate(); | |
505 } | |
506 | |
507 a0.fPos = a; | |
508 a0.fPos += abN; | |
509 a1.fPos = a; | |
510 a1.fPos -= abN; | |
511 | |
512 c0.fPos = c; | |
513 c0.fPos += cbN; | |
514 c1.fPos = c; | |
515 c1.fPos -= cbN; | |
516 | |
517 intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos); | |
518 | |
519 if (toSrc) { | |
520 toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(BezierVertex), kQuadNu
mVertices); | |
521 } | |
522 } | |
523 | |
524 // Equations based off of Loop-Blinn Quadratic GPU Rendering | |
525 // Input Parametric: | |
526 // 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) | |
527 // Output Implicit: | |
528 // f(x, y, w) = f(P) = K^2 - LM | |
529 // K = dot(k, P), L = dot(l, P), M = dot(m, P) | |
530 // k, l, m are calculated in function GrPathUtils::getConicKLM | |
531 static void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kQuadNumVert
ices], | |
532 const SkScalar weight) { | |
533 SkScalar klm[9]; | |
534 | |
535 GrPathUtils::getConicKLM(p, weight, klm); | |
536 | |
537 for (int i = 0; i < kQuadNumVertices; ++i) { | |
538 const SkPoint pnt = verts[i].fPos; | |
539 verts[i].fConic.fK = pnt.fX * klm[0] + pnt.fY * klm[1] + klm[2]; | |
540 verts[i].fConic.fL = pnt.fX * klm[3] + pnt.fY * klm[4] + klm[5]; | |
541 verts[i].fConic.fM = pnt.fX * klm[6] + pnt.fY * klm[7] + klm[8]; | |
542 } | |
543 } | |
544 | |
545 static void add_conics(const SkPoint p[3], | |
546 const SkScalar weight, | |
547 const SkMatrix* toDevice, | |
548 const SkMatrix* toSrc, | |
549 BezierVertex** vert) { | |
550 bloat_quad(p, toDevice, toSrc, *vert); | |
551 set_conic_coeffs(p, *vert, weight); | |
552 *vert += kQuadNumVertices; | |
553 } | |
554 | |
555 static void add_quads(const SkPoint p[3], | |
556 int subdiv, | |
557 const SkMatrix* toDevice, | |
558 const SkMatrix* toSrc, | |
559 BezierVertex** vert) { | |
560 SkASSERT(subdiv >= 0); | |
561 if (subdiv) { | |
562 SkPoint newP[5]; | |
563 SkChopQuadAtHalf(p, newP); | |
564 add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert); | |
565 add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert); | |
566 } else { | |
567 bloat_quad(p, toDevice, toSrc, *vert); | |
568 set_uv_quad(p, *vert); | |
569 *vert += kQuadNumVertices; | |
570 } | |
571 } | |
572 | |
573 static void add_line(const SkPoint p[2], | |
574 const SkMatrix* toSrc, | |
575 uint8_t coverage, | |
576 LineVertex** vert) { | |
577 const SkPoint& a = p[0]; | |
578 const SkPoint& b = p[1]; | |
579 | |
580 SkVector ortho, vec = b; | |
581 vec -= a; | |
582 | |
583 if (vec.setLength(SK_ScalarHalf)) { | |
584 // Create a vector orthogonal to 'vec' and of unit length | |
585 ortho.fX = 2.0f * vec.fY; | |
586 ortho.fY = -2.0f * vec.fX; | |
587 | |
588 float floatCoverage = GrNormalizeByteToFloat(coverage); | |
589 | |
590 (*vert)[0].fPos = a; | |
591 (*vert)[0].fCoverage = floatCoverage; | |
592 (*vert)[1].fPos = b; | |
593 (*vert)[1].fCoverage = floatCoverage; | |
594 (*vert)[2].fPos = a - vec + ortho; | |
595 (*vert)[2].fCoverage = 0; | |
596 (*vert)[3].fPos = b + vec + ortho; | |
597 (*vert)[3].fCoverage = 0; | |
598 (*vert)[4].fPos = a - vec - ortho; | |
599 (*vert)[4].fCoverage = 0; | |
600 (*vert)[5].fPos = b + vec - ortho; | |
601 (*vert)[5].fCoverage = 0; | |
602 | |
603 if (toSrc) { | |
604 toSrc->mapPointsWithStride(&(*vert)->fPos, | |
605 sizeof(LineVertex), | |
606 kLineSegNumVertices); | |
607 } | |
608 } else { | |
609 // just make it degenerate and likely offscreen | |
610 for (int i = 0; i < kLineSegNumVertices; ++i) { | |
611 (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax); | |
612 } | |
613 } | |
614 | |
615 *vert += kLineSegNumVertices; | |
616 } | |
617 | |
618 /////////////////////////////////////////////////////////////////////////////// | |
619 | |
620 bool GrAAHairLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const
{ | |
621 if (!args.fAntiAlias) { | |
622 return false; | |
623 } | |
624 | |
625 if (!IsStrokeHairlineOrEquivalent(*args.fStroke, *args.fViewMatrix, nullptr)
) { | |
626 return false; | |
627 } | |
628 | |
629 if (SkPath::kLine_SegmentMask == args.fPath->getSegmentMasks() || | |
630 args.fShaderCaps->shaderDerivativeSupport()) { | |
631 return true; | |
632 } | |
633 return false; | |
634 } | |
635 | |
636 template <class VertexType> | |
637 bool check_bounds(const SkMatrix& viewMatrix, const SkRect& devBounds, void* ver
tices, int vCount) | |
638 { | |
639 SkRect tolDevBounds = devBounds; | |
640 // The bounds ought to be tight, but in perspective the below code runs the
verts | |
641 // through the view matrix to get back to dev coords, which can introduce im
precision. | |
642 if (viewMatrix.hasPerspective()) { | |
643 tolDevBounds.outset(SK_Scalar1 / 1000, SK_Scalar1 / 1000); | |
644 } else { | |
645 // Non-persp matrices cause this path renderer to draw in device space. | |
646 SkASSERT(viewMatrix.isIdentity()); | |
647 } | |
648 SkRect actualBounds; | |
649 | |
650 VertexType* verts = reinterpret_cast<VertexType*>(vertices); | |
651 bool first = true; | |
652 for (int i = 0; i < vCount; ++i) { | |
653 SkPoint pos = verts[i].fPos; | |
654 // This is a hack to workaround the fact that we move some degenerate se
gments offscreen. | |
655 if (SK_ScalarMax == pos.fX) { | |
656 continue; | |
657 } | |
658 viewMatrix.mapPoints(&pos, 1); | |
659 if (first) { | |
660 actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY); | |
661 first = false; | |
662 } else { | |
663 actualBounds.growToInclude(pos.fX, pos.fY); | |
664 } | |
665 } | |
666 if (!first) { | |
667 return tolDevBounds.contains(actualBounds); | |
668 } | |
669 | |
670 return true; | |
671 } | |
672 | |
673 class AAHairlineBatch : public GrVertexBatch { | |
674 public: | |
675 struct Geometry { | |
676 GrColor fColor; | |
677 uint8_t fCoverage; | |
678 SkMatrix fViewMatrix; | |
679 SkPath fPath; | |
680 SkIRect fDevClipBounds; | |
681 }; | |
682 | |
683 static GrDrawBatch* Create(const Geometry& geometry) { return new AAHairline
Batch(geometry); } | |
684 | |
685 const char* name() const override { return "AAHairlineBatch"; } | |
686 | |
687 void getInvariantOutputColor(GrInitInvariantOutput* out) const override { | |
688 // When this is called on a batch, there is only one geometry bundle | |
689 out->setKnownFourComponents(fGeoData[0].fColor); | |
690 } | |
691 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override { | |
692 out->setUnknownSingleComponent(); | |
693 } | |
694 | |
695 private: | |
696 void initBatchTracker(const GrPipelineOptimizations& opt) override { | |
697 // Handle any color overrides | |
698 if (!opt.readsColor()) { | |
699 fGeoData[0].fColor = GrColor_ILLEGAL; | |
700 } | |
701 opt.getOverrideColorIfSet(&fGeoData[0].fColor); | |
702 | |
703 // setup batch properties | |
704 fBatch.fColorIgnored = !opt.readsColor(); | |
705 fBatch.fColor = fGeoData[0].fColor; | |
706 fBatch.fUsesLocalCoords = opt.readsLocalCoords(); | |
707 fBatch.fCoverageIgnored = !opt.readsCoverage(); | |
708 fBatch.fCoverage = fGeoData[0].fCoverage; | |
709 } | |
710 | |
711 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } | |
712 | |
713 void onPrepareDraws(Target*) override; | |
714 | |
715 typedef SkTArray<SkPoint, true> PtArray; | |
716 typedef SkTArray<int, true> IntArray; | |
717 typedef SkTArray<float, true> FloatArray; | |
718 | |
719 AAHairlineBatch(const Geometry& geometry) { | |
720 this->initClassID<AAHairlineBatch>(); | |
721 fGeoData.push_back(geometry); | |
722 | |
723 // compute bounds | |
724 fBounds = geometry.fPath.getBounds(); | |
725 geometry.fViewMatrix.mapRect(&fBounds); | |
726 | |
727 // This is b.c. hairlines are notionally infinitely thin so without expa
nsion | |
728 // two overlapping lines could be reordered even though they hit the sam
e pixels. | |
729 fBounds.outset(0.5f, 0.5f); | |
730 } | |
731 | |
732 bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { | |
733 AAHairlineBatch* that = t->cast<AAHairlineBatch>(); | |
734 | |
735 if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pi
peline(), | |
736 that->bounds(), caps)) { | |
737 return false; | |
738 } | |
739 | |
740 if (this->viewMatrix().hasPerspective() != that->viewMatrix().hasPerspec
tive()) { | |
741 return false; | |
742 } | |
743 | |
744 // We go to identity if we don't have perspective | |
745 if (this->viewMatrix().hasPerspective() && | |
746 !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { | |
747 return false; | |
748 } | |
749 | |
750 // TODO we can actually batch hairlines if they are the same color in a
kind of bulk method | |
751 // but we haven't implemented this yet | |
752 // TODO investigate going to vertex color and coverage? | |
753 if (this->coverage() != that->coverage()) { | |
754 return false; | |
755 } | |
756 | |
757 if (this->color() != that->color()) { | |
758 return false; | |
759 } | |
760 | |
761 SkASSERT(this->usesLocalCoords() == that->usesLocalCoords()); | |
762 if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->vi
ewMatrix())) { | |
763 return false; | |
764 } | |
765 | |
766 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin())
; | |
767 this->joinBounds(that->bounds()); | |
768 return true; | |
769 } | |
770 | |
771 GrColor color() const { return fBatch.fColor; } | |
772 uint8_t coverage() const { return fBatch.fCoverage; } | |
773 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } | |
774 const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; } | |
775 bool coverageIgnored() const { return fBatch.fCoverageIgnored; } | |
776 | |
777 struct BatchTracker { | |
778 GrColor fColor; | |
779 uint8_t fCoverage; | |
780 SkRect fDevBounds; | |
781 bool fUsesLocalCoords; | |
782 bool fColorIgnored; | |
783 bool fCoverageIgnored; | |
784 }; | |
785 | |
786 BatchTracker fBatch; | |
787 SkSTArray<1, Geometry, true> fGeoData; | |
788 }; | |
789 | |
790 void AAHairlineBatch::onPrepareDraws(Target* target) { | |
791 // Setup the viewmatrix and localmatrix for the GrGeometryProcessor. | |
792 SkMatrix invert; | |
793 if (!this->viewMatrix().invert(&invert)) { | |
794 return; | |
795 } | |
796 | |
797 // we will transform to identity space if the viewmatrix does not have persp
ective | |
798 bool hasPerspective = this->viewMatrix().hasPerspective(); | |
799 const SkMatrix* geometryProcessorViewM = &SkMatrix::I(); | |
800 const SkMatrix* geometryProcessorLocalM = &invert; | |
801 const SkMatrix* toDevice = nullptr; | |
802 const SkMatrix* toSrc = nullptr; | |
803 if (hasPerspective) { | |
804 geometryProcessorViewM = &this->viewMatrix(); | |
805 geometryProcessorLocalM = &SkMatrix::I(); | |
806 toDevice = &this->viewMatrix(); | |
807 toSrc = &invert; | |
808 } | |
809 | |
810 SkAutoTUnref<const GrGeometryProcessor> lineGP; | |
811 { | |
812 using namespace GrDefaultGeoProcFactory; | |
813 | |
814 Color color(this->color()); | |
815 Coverage coverage(Coverage::kAttribute_Type); | |
816 LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosit
ion_Type : | |
817 LocalCoords::kUnused_T
ype); | |
818 localCoords.fMatrix = geometryProcessorLocalM; | |
819 lineGP.reset(GrDefaultGeoProcFactory::Create(color, coverage, localCoord
s, | |
820 *geometryProcessorViewM)); | |
821 } | |
822 | |
823 SkAutoTUnref<const GrGeometryProcessor> quadGP( | |
824 GrQuadEffect::Create(this->color(), | |
825 *geometryProcessorViewM, | |
826 kHairlineAA_GrProcessorEdgeType, | |
827 target->caps(), | |
828 *geometryProcessorLocalM, | |
829 this->usesLocalCoords(), | |
830 this->coverage())); | |
831 | |
832 SkAutoTUnref<const GrGeometryProcessor> conicGP( | |
833 GrConicEffect::Create(this->color(), | |
834 *geometryProcessorViewM, | |
835 kHairlineAA_GrProcessorEdgeType, | |
836 target->caps(), | |
837 *geometryProcessorLocalM, | |
838 this->usesLocalCoords(), | |
839 this->coverage())); | |
840 | |
841 // This is hand inlined for maximum performance. | |
842 PREALLOC_PTARRAY(128) lines; | |
843 PREALLOC_PTARRAY(128) quads; | |
844 PREALLOC_PTARRAY(128) conics; | |
845 IntArray qSubdivs; | |
846 FloatArray cWeights; | |
847 int quadCount = 0; | |
848 | |
849 int instanceCount = fGeoData.count(); | |
850 for (int i = 0; i < instanceCount; i++) { | |
851 const Geometry& args = fGeoData[i]; | |
852 quadCount += gather_lines_and_quads(args.fPath, args.fViewMatrix, args.f
DevClipBounds, | |
853 &lines, &quads, &conics, &qSubdivs,
&cWeights); | |
854 } | |
855 | |
856 int lineCount = lines.count() / 2; | |
857 int conicCount = conics.count() / 3; | |
858 | |
859 // do lines first | |
860 if (lineCount) { | |
861 SkAutoTUnref<const GrIndexBuffer> linesIndexBuffer( | |
862 ref_lines_index_buffer(target->resourceProvider())); | |
863 target->initDraw(lineGP, this->pipeline()); | |
864 | |
865 const GrVertexBuffer* vertexBuffer; | |
866 int firstVertex; | |
867 | |
868 size_t vertexStride = lineGP->getVertexStride(); | |
869 int vertexCount = kLineSegNumVertices * lineCount; | |
870 LineVertex* verts = reinterpret_cast<LineVertex*>( | |
871 target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &f
irstVertex)); | |
872 | |
873 if (!verts|| !linesIndexBuffer) { | |
874 SkDebugf("Could not allocate vertices\n"); | |
875 return; | |
876 } | |
877 | |
878 SkASSERT(lineGP->getVertexStride() == sizeof(LineVertex)); | |
879 | |
880 for (int i = 0; i < lineCount; ++i) { | |
881 add_line(&lines[2*i], toSrc, this->coverage(), &verts); | |
882 } | |
883 | |
884 { | |
885 GrVertices vertices; | |
886 vertices.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, lin
esIndexBuffer, | |
887 firstVertex, kLineSegNumVertices, kIdxsPerLin
eSeg, lineCount, | |
888 kLineSegsNumInIdxBuffer); | |
889 target->draw(vertices); | |
890 } | |
891 } | |
892 | |
893 if (quadCount || conicCount) { | |
894 const GrVertexBuffer* vertexBuffer; | |
895 int firstVertex; | |
896 | |
897 SkAutoTUnref<const GrIndexBuffer> quadsIndexBuffer( | |
898 ref_quads_index_buffer(target->resourceProvider())); | |
899 | |
900 size_t vertexStride = sizeof(BezierVertex); | |
901 int vertexCount = kQuadNumVertices * quadCount + kQuadNumVertices * coni
cCount; | |
902 void *vertices = target->makeVertexSpace(vertexStride, vertexCount, | |
903 &vertexBuffer, &firstVertex); | |
904 | |
905 if (!vertices || !quadsIndexBuffer) { | |
906 SkDebugf("Could not allocate vertices\n"); | |
907 return; | |
908 } | |
909 | |
910 // Setup vertices | |
911 BezierVertex* verts = reinterpret_cast<BezierVertex*>(vertices); | |
912 | |
913 int unsubdivQuadCnt = quads.count() / 3; | |
914 for (int i = 0; i < unsubdivQuadCnt; ++i) { | |
915 SkASSERT(qSubdivs[i] >= 0); | |
916 add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts); | |
917 } | |
918 | |
919 // Start Conics | |
920 for (int i = 0; i < conicCount; ++i) { | |
921 add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts); | |
922 } | |
923 | |
924 if (quadCount > 0) { | |
925 target->initDraw(quadGP, this->pipeline()); | |
926 | |
927 { | |
928 GrVertices verts; | |
929 verts.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, qu
adsIndexBuffer, | |
930 firstVertex, kQuadNumVertices, kIdxsPerQuad,
quadCount, | |
931 kQuadsNumInIdxBuffer); | |
932 target->draw(verts); | |
933 firstVertex += quadCount * kQuadNumVertices; | |
934 } | |
935 } | |
936 | |
937 if (conicCount > 0) { | |
938 target->initDraw(conicGP, this->pipeline()); | |
939 | |
940 { | |
941 GrVertices verts; | |
942 verts.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, qu
adsIndexBuffer, | |
943 firstVertex, kQuadNumVertices, kIdxsPerQuad,
conicCount, | |
944 kQuadsNumInIdxBuffer); | |
945 target->draw(verts); | |
946 } | |
947 } | |
948 } | |
949 } | |
950 | |
951 static GrDrawBatch* create_hairline_batch(GrColor color, | |
952 const SkMatrix& viewMatrix, | |
953 const SkPath& path, | |
954 const GrStrokeInfo& stroke, | |
955 const SkIRect& devClipBounds) { | |
956 SkScalar hairlineCoverage; | |
957 uint8_t newCoverage = 0xff; | |
958 if (GrPathRenderer::IsStrokeHairlineOrEquivalent(stroke, viewMatrix, &hairli
neCoverage)) { | |
959 newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); | |
960 } | |
961 | |
962 AAHairlineBatch::Geometry geometry; | |
963 geometry.fColor = color; | |
964 geometry.fCoverage = newCoverage; | |
965 geometry.fViewMatrix = viewMatrix; | |
966 geometry.fPath = path; | |
967 geometry.fDevClipBounds = devClipBounds; | |
968 | |
969 return AAHairlineBatch::Create(geometry); | |
970 } | |
971 | |
972 bool GrAAHairLinePathRenderer::onDrawPath(const DrawPathArgs& args) { | |
973 SkIRect devClipBounds; | |
974 args.fPipelineBuilder->clip().getConservativeBounds(args.fPipelineBuilder->g
etRenderTarget(), | |
975 &devClipBounds); | |
976 | |
977 SkAutoTUnref<GrDrawBatch> batch(create_hairline_batch(args.fColor, *args.fVi
ewMatrix, *args.fPath, | |
978 *args.fStroke, devClip
Bounds)); | |
979 args.fTarget->drawBatch(*args.fPipelineBuilder, batch); | |
980 | |
981 return true; | |
982 } | |
983 | |
984 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
985 | |
986 #ifdef GR_TEST_UTILS | |
987 | |
988 DRAW_BATCH_TEST_DEFINE(AAHairlineBatch) { | |
989 GrColor color = GrRandomColor(random); | |
990 SkMatrix viewMatrix = GrTest::TestMatrix(random); | |
991 GrStrokeInfo stroke(SkStrokeRec::kHairline_InitStyle); | |
992 SkPath path = GrTest::TestPath(random); | |
993 SkIRect devClipBounds; | |
994 devClipBounds.setEmpty(); | |
995 return create_hairline_batch(color, viewMatrix, path, stroke, devClipBounds)
; | |
996 } | |
997 | |
998 #endif | |
OLD | NEW |