blob: d5962849f0a4c1ada823d3d0ded0e79a43db04d5 [file] [log] [blame]
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +00001
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "GrAAConvexPathRenderer.h"
10
11#include "GrContext.h"
12#include "GrDrawState.h"
13#include "GrPathUtils.h"
14#include "SkString.h"
15#include "SkTrace.h"
16
17
18GrAAConvexPathRenderer::GrAAConvexPathRenderer() {
19}
20
21bool GrAAConvexPathRenderer::canDrawPath(const GrDrawTarget::Caps& targetCaps,
22 const SkPath& path,
23 GrPathFill fill,
24 bool antiAlias) const {
25 return targetCaps.fShaderDerivativeSupport && antiAlias &&
26 kHairLine_PathFill != fill && !GrIsFillInverted(fill) &&
27 path.isConvex();
28}
29
30namespace {
31
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000032struct Segment {
33 enum {
34 kLine,
35 kQuad
36 } fType;
bsalomon@google.com9aed1142012-01-30 14:28:39 +000037 // line uses one pt, quad uses 2 pts
38 GrPoint fPts[2];
39 // normal to edge ending at each pt
40 GrVec fNorms[2];
41 // is the corner where the previous segment meets this segment
42 // sharp. If so, fMid is a normalized bisector facing outward.
43 GrVec fMid;
44
45 int countPoints() {
46 return (kLine == fType) ? 1 : 2;
47 }
48 const SkPoint& endPt() const {
49 return (kLine == fType) ? fPts[0] : fPts[1];
50 };
51 const SkPoint& endNorm() const {
52 return (kLine == fType) ? fNorms[0] : fNorms[1];
53 };
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000054};
55
56typedef SkTArray<Segment, true> SegmentArray;
57
bsalomon@google.com9aed1142012-01-30 14:28:39 +000058void center_of_mass(const SegmentArray& segments, SkPoint* c) {
59 GrScalar area = 0;
60 SkPoint center;
61 center.set(0, 0);
62 int count = segments.count();
bsalomon@google.com5b56d9e2012-02-23 19:18:37 +000063 SkPoint p0;
64 if (count > 2) {
65 // We translate the polygon so that the first point is at the origin.
66 // This avoids some precision issues with small area polygons far away
67 // from the origin.
68 p0 = segments[0].endPt();
69 SkPoint pi;
70 SkPoint pj;
71 // the first and last interation of the below loop would compute
72 // zeros since the starting / ending point is (0,0). So instead we start
73 // at i=1 and make the last iteration i=count-2.
74 pj = segments[1].endPt() - p0;
75 for (int i = 1; i < count - 1; ++i) {
76 pi = pj;
77 const SkPoint pj = segments[i + 1].endPt() - p0;
78
79 GrScalar t = GrMul(pi.fX, pj.fY) - GrMul(pj.fX, pi.fY);
80 area += t;
81 center.fX += (pi.fX + pj.fX) * t;
82 center.fY += (pi.fY + pj.fY) * t;
83
84 }
bsalomon@google.com9aed1142012-01-30 14:28:39 +000085 }
bsalomon@google.com278dc692012-02-15 16:52:51 +000086 // If the poly has no area then we instead return the average of
87 // its points.
bsalomon@google.com5b56d9e2012-02-23 19:18:37 +000088 if (SkScalarNearlyZero(area)) {
bsalomon@google.com278dc692012-02-15 16:52:51 +000089 SkPoint avg;
90 avg.set(0, 0);
91 for (int i = 0; i < count; ++i) {
92 const SkPoint& pt = segments[i].endPt();
93 avg.fX += pt.fX;
94 avg.fY += pt.fY;
95 }
96 SkScalar denom = SK_Scalar1 / count;
97 avg.scale(denom);
98 *c = avg;
99 } else {
100 area *= 3;
101 area = GrScalarDiv(GR_Scalar1, area);
102 center.fX = GrScalarMul(center.fX, area);
103 center.fY = GrScalarMul(center.fY, area);
bsalomon@google.com5b56d9e2012-02-23 19:18:37 +0000104 // undo the translate of p0 to the origin.
105 *c = center + p0;
bsalomon@google.com278dc692012-02-15 16:52:51 +0000106 }
107 GrAssert(!SkScalarIsNaN(c->fX) && !SkScalarIsNaN(c->fY));
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000108}
109
110void compute_vectors(SegmentArray* segments,
bsalomon@google.com278dc692012-02-15 16:52:51 +0000111 SkPoint* fanPt,
112 SkPath::Direction dir,
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000113 int* vCount,
114 int* iCount) {
115 center_of_mass(*segments, fanPt);
116 int count = segments->count();
117
bsalomon@google.com278dc692012-02-15 16:52:51 +0000118 // Make the normals point towards the outside
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000119 GrPoint::Side normSide;
bsalomon@google.com278dc692012-02-15 16:52:51 +0000120 if (dir == SkPath::kCCW_Direction) {
121 normSide = GrPoint::kRight_Side;
122 } else {
123 normSide = GrPoint::kLeft_Side;
124 }
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000125
126 *vCount = 0;
127 *iCount = 0;
128 // compute normals at all points
129 for (int a = 0; a < count; ++a) {
130 const Segment& sega = (*segments)[a];
131 int b = (a + 1) % count;
132 Segment& segb = (*segments)[b];
133
134 const GrPoint* prevPt = &sega.endPt();
135 int n = segb.countPoints();
136 for (int p = 0; p < n; ++p) {
137 segb.fNorms[p] = segb.fPts[p] - *prevPt;
138 segb.fNorms[p].normalize();
139 segb.fNorms[p].setOrthog(segb.fNorms[p], normSide);
140 prevPt = &segb.fPts[p];
141 }
142 if (Segment::kLine == segb.fType) {
143 *vCount += 5;
144 *iCount += 9;
145 } else {
146 *vCount += 6;
147 *iCount += 12;
148 }
149 }
150
151 // compute mid-vectors where segments meet. TODO: Detect shallow corners
152 // and leave out the wedges and close gaps by stitching segments together.
153 for (int a = 0; a < count; ++a) {
154 const Segment& sega = (*segments)[a];
155 int b = (a + 1) % count;
156 Segment& segb = (*segments)[b];
157 segb.fMid = segb.fNorms[0] + sega.endNorm();
158 segb.fMid.normalize();
159 // corner wedges
160 *vCount += 4;
161 *iCount += 6;
162 }
163}
164
bsalomon@google.com9732f622012-01-31 15:19:21 +0000165struct DegenerateTestData {
166 DegenerateTestData() { fStage = kInitial; }
167 bool isDegenerate() const { return kNonDegenerate != fStage; }
168 enum {
169 kInitial,
170 kPoint,
171 kLine,
172 kNonDegenerate
173 } fStage;
174 GrPoint fFirstPoint;
175 GrVec fLineNormal;
176 GrScalar fLineC;
177};
178
179void update_degenerate_test(DegenerateTestData* data, const GrPoint& pt) {
180 static const SkScalar TOL = (SK_Scalar1 / 16);
181 static const SkScalar TOL_SQD = SkScalarMul(TOL, TOL);
182
183 switch (data->fStage) {
184 case DegenerateTestData::kInitial:
185 data->fFirstPoint = pt;
186 data->fStage = DegenerateTestData::kPoint;
187 break;
188 case DegenerateTestData::kPoint:
189 if (pt.distanceToSqd(data->fFirstPoint) > TOL_SQD) {
190 data->fLineNormal = pt - data->fFirstPoint;
191 data->fLineNormal.normalize();
192 data->fLineNormal.setOrthog(data->fLineNormal);
193 data->fLineC = -data->fLineNormal.dot(data->fFirstPoint);
194 data->fStage = DegenerateTestData::kLine;
195 }
196 break;
197 case DegenerateTestData::kLine:
198 if (SkScalarAbs(data->fLineNormal.dot(pt) + data->fLineC) > TOL) {
199 data->fStage = DegenerateTestData::kNonDegenerate;
200 }
201 case DegenerateTestData::kNonDegenerate:
202 break;
203 default:
204 GrCrash("Unexpected degenerate test stage.");
205 }
206}
207
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000208bool get_segments(const GrPath& path,
209 SegmentArray* segments,
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000210 SkPoint* fanPt,
211 int* vCount,
212 int* iCount) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000213 SkPath::Iter iter(path, true);
bsalomon@google.com5cc90d12012-01-17 16:28:34 +0000214 // This renderer overemphasises very thin path regions. We use the distance
215 // to the path from the sample to compute coverage. Every pixel intersected
216 // by the path will be hit and the maximum distance is sqrt(2)/2. We don't
217 // notice that the sample may be close to a very thin area of the path and
218 // thus should be very light. This is particularly egregious for degenerate
219 // line paths. We detect paths that are very close to a line (zero area) and
220 // draw nothing.
bsalomon@google.com9732f622012-01-31 15:19:21 +0000221 DegenerateTestData degenerateData;
222
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000223 for (;;) {
224 GrPoint pts[4];
225 GrPathCmd cmd = (GrPathCmd)iter.next(pts);
226 switch (cmd) {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000227 case kMove_PathCmd:
228 update_degenerate_test(&degenerateData, pts[0]);
229 break;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000230 case kLine_PathCmd: {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000231 update_degenerate_test(&degenerateData, pts[1]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000232 segments->push_back();
233 segments->back().fType = Segment::kLine;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000234 segments->back().fPts[0] = pts[1];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000235 break;
236 }
237 case kQuadratic_PathCmd:
bsalomon@google.com9732f622012-01-31 15:19:21 +0000238 update_degenerate_test(&degenerateData, pts[1]);
239 update_degenerate_test(&degenerateData, pts[2]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000240 segments->push_back();
241 segments->back().fType = Segment::kQuad;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000242 segments->back().fPts[0] = pts[1];
243 segments->back().fPts[1] = pts[2];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000244 break;
245 case kCubic_PathCmd: {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000246 update_degenerate_test(&degenerateData, pts[1]);
247 update_degenerate_test(&degenerateData, pts[2]);
248 update_degenerate_test(&degenerateData, pts[3]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000249 SkSTArray<15, SkPoint, true> quads;
250 GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, &quads);
251 int count = quads.count();
252 for (int q = 0; q < count; q += 3) {
253 segments->push_back();
254 segments->back().fType = Segment::kQuad;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000255 segments->back().fPts[0] = quads[q + 1];
256 segments->back().fPts[1] = quads[q + 2];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000257 }
258 break;
259 };
260 case kEnd_PathCmd:
bsalomon@google.com9732f622012-01-31 15:19:21 +0000261 if (degenerateData.isDegenerate()) {
262 return false;
263 } else {
bsalomon@google.com278dc692012-02-15 16:52:51 +0000264 SkPath::Direction dir;
265 GR_DEBUGCODE(bool succeeded = )
266 path.cheapComputeDirection(&dir);
267 GrAssert(succeeded);
268 compute_vectors(segments, fanPt, dir, vCount, iCount);
bsalomon@google.com9732f622012-01-31 15:19:21 +0000269 return true;
270 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000271 default:
272 break;
273 }
274 }
275}
276
277struct QuadVertex {
278 GrPoint fPos;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000279 GrPoint fUV;
280 GrScalar fD0;
281 GrScalar fD1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000282};
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000283
284void create_vertices(const SegmentArray& segments,
285 const SkPoint& fanPt,
286 QuadVertex* verts,
287 uint16_t* idxs) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000288 int v = 0;
289 int i = 0;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000290
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000291 int count = segments.count();
292 for (int a = 0; a < count; ++a) {
293 const Segment& sega = segments[a];
294 int b = (a + 1) % count;
295 const Segment& segb = segments[b];
296
297 // FIXME: These tris are inset in the 1 unit arc around the corner
298 verts[v + 0].fPos = sega.endPt();
299 verts[v + 1].fPos = verts[v + 0].fPos + sega.endNorm();
300 verts[v + 2].fPos = verts[v + 0].fPos + segb.fMid;
301 verts[v + 3].fPos = verts[v + 0].fPos + segb.fNorms[0];
302 verts[v + 0].fUV.set(0,0);
303 verts[v + 1].fUV.set(0,-SK_Scalar1);
304 verts[v + 2].fUV.set(0,-SK_Scalar1);
305 verts[v + 3].fUV.set(0,-SK_Scalar1);
306 verts[v + 0].fD0 = verts[v + 0].fD1 = -SK_Scalar1;
307 verts[v + 1].fD0 = verts[v + 1].fD1 = -SK_Scalar1;
308 verts[v + 2].fD0 = verts[v + 2].fD1 = -SK_Scalar1;
309 verts[v + 3].fD0 = verts[v + 3].fD1 = -SK_Scalar1;
310
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000311 idxs[i + 0] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000312 idxs[i + 1] = v + 2;
313 idxs[i + 2] = v + 1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000314 idxs[i + 3] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000315 idxs[i + 4] = v + 3;
316 idxs[i + 5] = v + 2;
317
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000318 v += 4;
319 i += 6;
320
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000321 if (Segment::kLine == segb.fType) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000322 verts[v + 0].fPos = fanPt;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000323 verts[v + 1].fPos = sega.endPt();
324 verts[v + 2].fPos = segb.fPts[0];
325
326 verts[v + 3].fPos = verts[v + 1].fPos + segb.fNorms[0];
327 verts[v + 4].fPos = verts[v + 2].fPos + segb.fNorms[0];
328
329 // we draw the line edge as a degenerate quad (u is 0, v is the
330 // signed distance to the edge)
331 GrScalar dist = fanPt.distanceToLineBetween(verts[v + 1].fPos,
332 verts[v + 2].fPos);
333 verts[v + 0].fUV.set(0, dist);
334 verts[v + 1].fUV.set(0, 0);
335 verts[v + 2].fUV.set(0, 0);
336 verts[v + 3].fUV.set(0, -SK_Scalar1);
337 verts[v + 4].fUV.set(0, -SK_Scalar1);
338
339 verts[v + 0].fD0 = verts[v + 0].fD1 = -SK_Scalar1;
340 verts[v + 1].fD0 = verts[v + 1].fD1 = -SK_Scalar1;
341 verts[v + 2].fD0 = verts[v + 2].fD1 = -SK_Scalar1;
342 verts[v + 3].fD0 = verts[v + 3].fD1 = -SK_Scalar1;
343 verts[v + 4].fD0 = verts[v + 4].fD1 = -SK_Scalar1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000344
345 idxs[i + 0] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000346 idxs[i + 1] = v + 2;
347 idxs[i + 2] = v + 1;
348
349 idxs[i + 3] = v + 3;
350 idxs[i + 4] = v + 1;
351 idxs[i + 5] = v + 2;
352
353 idxs[i + 6] = v + 4;
354 idxs[i + 7] = v + 3;
bsalomon@google.com06809612012-01-21 15:03:39 +0000355 idxs[i + 8] = v + 2;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000356
bsalomon@google.com06809612012-01-21 15:03:39 +0000357 v += 5;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000358 i += 9;
bsalomon@google.com06809612012-01-21 15:03:39 +0000359 } else {
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000360 GrPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]};
bsalomon@google.com495e2102012-01-21 14:48:36 +0000361
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000362 GrVec midVec = segb.fNorms[0] + segb.fNorms[1];
363 midVec.normalize();
bsalomon@google.com06809612012-01-21 15:03:39 +0000364
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000365 verts[v + 0].fPos = fanPt;
366 verts[v + 1].fPos = qpts[0];
367 verts[v + 2].fPos = qpts[2];
368 verts[v + 3].fPos = qpts[0] + segb.fNorms[0];
369 verts[v + 4].fPos = qpts[2] + segb.fNorms[1];
370 verts[v + 5].fPos = qpts[1] + midVec;
371
372 GrScalar c = segb.fNorms[0].dot(qpts[0]);
373 verts[v + 0].fD0 = -segb.fNorms[0].dot(fanPt) + c;
374 verts[v + 1].fD0 = 0.f;
375 verts[v + 2].fD0 = -segb.fNorms[0].dot(qpts[2]) + c;
376 verts[v + 3].fD0 = -GR_ScalarMax/100;
377 verts[v + 4].fD0 = -GR_ScalarMax/100;
378 verts[v + 5].fD0 = -GR_ScalarMax/100;
379
380 c = segb.fNorms[1].dot(qpts[2]);
381 verts[v + 0].fD1 = -segb.fNorms[1].dot(fanPt) + c;
382 verts[v + 1].fD1 = -segb.fNorms[1].dot(qpts[0]) + c;
383 verts[v + 2].fD1 = 0.f;
384 verts[v + 3].fD1 = -GR_ScalarMax/100;
385 verts[v + 4].fD1 = -GR_ScalarMax/100;
386 verts[v + 5].fD1 = -GR_ScalarMax/100;
387
bsalomon@google.com06809612012-01-21 15:03:39 +0000388 GrMatrix toUV;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000389 GrPathUtils::quadDesignSpaceToUVCoordsMatrix(qpts, &toUV);
390 toUV.mapPointsWithStride(&verts[v].fUV,
391 &verts[v].fPos,
392 sizeof(QuadVertex),
393 6);
bsalomon@google.com06809612012-01-21 15:03:39 +0000394
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000395 idxs[i + 0] = v + 3;
396 idxs[i + 1] = v + 1;
397 idxs[i + 2] = v + 2;
398 idxs[i + 3] = v + 4;
399 idxs[i + 4] = v + 3;
400 idxs[i + 5] = v + 2;
bsalomon@google.com06809612012-01-21 15:03:39 +0000401
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000402 idxs[i + 6] = v + 5;
403 idxs[i + 7] = v + 3;
404 idxs[i + 8] = v + 4;
405
406 idxs[i + 9] = v + 0;
407 idxs[i + 10] = v + 2;
408 idxs[i + 11] = v + 1;
409
410 v += 6;
411 i += 12;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000412 }
413 }
414}
415
416}
417
418void GrAAConvexPathRenderer::drawPath(GrDrawState::StageMask stageMask) {
419 GrAssert(fPath->isConvex());
420 if (fPath->isEmpty()) {
421 return;
422 }
423 GrDrawState* drawState = fTarget->drawState();
424
425 GrDrawTarget::AutoStateRestore asr;
426 GrMatrix vm = drawState->getViewMatrix();
427 vm.postTranslate(fTranslate.fX, fTranslate.fY);
428 asr.set(fTarget);
429 GrMatrix ivm;
430 if (vm.invert(&ivm)) {
431 drawState->preConcatSamplerMatrices(stageMask, ivm);
432 }
433 drawState->setViewMatrix(GrMatrix::I());
434
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000435 SkPath path;
436 fPath->transform(vm, &path);
437
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000438 GrVertexLayout layout = 0;
439 for (int s = 0; s < GrDrawState::kNumStages; ++s) {
440 if ((1 << s) & stageMask) {
441 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
442 }
443 }
444 layout |= GrDrawTarget::kEdge_VertexLayoutBit;
445
446 QuadVertex *verts;
447 uint16_t* idxs;
448
bsalomon@google.com06809612012-01-21 15:03:39 +0000449 int vCount;
450 int iCount;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000451 SegmentArray segments;
452 SkPoint fanPt;
453 if (!get_segments(path, &segments, &fanPt, &vCount, &iCount)) {
454 return;
455 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000456
457 if (!fTarget->reserveVertexSpace(layout,
458 vCount,
459 reinterpret_cast<void**>(&verts))) {
460 return;
461 }
462 if (!fTarget->reserveIndexSpace(iCount, reinterpret_cast<void**>(&idxs))) {
463 fTarget->resetVertexSource();
464 return;
465 }
466
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000467 create_vertices(segments, fanPt, verts, idxs);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000468
469 drawState->setVertexEdgeType(GrDrawState::kQuad_EdgeType);
470 fTarget->drawIndexed(kTriangles_PrimitiveType,
471 0, // start vertex
472 0, // start index
473 vCount,
474 iCount);
475}
476