blob: 1a8a38904c556a284238eb8fed795ed4373dd87f [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();
63 for (int i = 0; i < count; ++i) {
64 const SkPoint& pi = segments[i].endPt();
65 int j = (i + 1) % count;
66 const SkPoint& pj = segments[j].endPt();
67 GrScalar t = GrMul(pi.fX, pj.fY) - GrMul(pj.fX, pi.fY);
68 area += t;
69 center.fX += (pi.fX + pj.fX) * t;
70 center.fY += (pi.fY + pj.fY) * t;
71 }
bsalomon@google.com278dc692012-02-15 16:52:51 +000072 // If the poly has no area then we instead return the average of
73 // its points.
74 if (SkScalarAbs(area) < SK_ScalarNearlyZero) {
75 SkPoint avg;
76 avg.set(0, 0);
77 for (int i = 0; i < count; ++i) {
78 const SkPoint& pt = segments[i].endPt();
79 avg.fX += pt.fX;
80 avg.fY += pt.fY;
81 }
82 SkScalar denom = SK_Scalar1 / count;
83 avg.scale(denom);
84 *c = avg;
85 } else {
86 area *= 3;
87 area = GrScalarDiv(GR_Scalar1, area);
88 center.fX = GrScalarMul(center.fX, area);
89 center.fY = GrScalarMul(center.fY, area);
90 *c = center;
91 }
92 GrAssert(!SkScalarIsNaN(c->fX) && !SkScalarIsNaN(c->fY));
bsalomon@google.com9aed1142012-01-30 14:28:39 +000093}
94
95void compute_vectors(SegmentArray* segments,
bsalomon@google.com278dc692012-02-15 16:52:51 +000096 SkPoint* fanPt,
97 SkPath::Direction dir,
bsalomon@google.com9aed1142012-01-30 14:28:39 +000098 int* vCount,
99 int* iCount) {
100 center_of_mass(*segments, fanPt);
101 int count = segments->count();
102
bsalomon@google.com278dc692012-02-15 16:52:51 +0000103 // Make the normals point towards the outside
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000104 GrPoint::Side normSide;
bsalomon@google.com278dc692012-02-15 16:52:51 +0000105 if (dir == SkPath::kCCW_Direction) {
106 normSide = GrPoint::kRight_Side;
107 } else {
108 normSide = GrPoint::kLeft_Side;
109 }
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000110
111 *vCount = 0;
112 *iCount = 0;
113 // compute normals at all points
114 for (int a = 0; a < count; ++a) {
115 const Segment& sega = (*segments)[a];
116 int b = (a + 1) % count;
117 Segment& segb = (*segments)[b];
118
119 const GrPoint* prevPt = &sega.endPt();
120 int n = segb.countPoints();
121 for (int p = 0; p < n; ++p) {
122 segb.fNorms[p] = segb.fPts[p] - *prevPt;
123 segb.fNorms[p].normalize();
124 segb.fNorms[p].setOrthog(segb.fNorms[p], normSide);
125 prevPt = &segb.fPts[p];
126 }
127 if (Segment::kLine == segb.fType) {
128 *vCount += 5;
129 *iCount += 9;
130 } else {
131 *vCount += 6;
132 *iCount += 12;
133 }
134 }
135
136 // compute mid-vectors where segments meet. TODO: Detect shallow corners
137 // and leave out the wedges and close gaps by stitching segments together.
138 for (int a = 0; a < count; ++a) {
139 const Segment& sega = (*segments)[a];
140 int b = (a + 1) % count;
141 Segment& segb = (*segments)[b];
142 segb.fMid = segb.fNorms[0] + sega.endNorm();
143 segb.fMid.normalize();
144 // corner wedges
145 *vCount += 4;
146 *iCount += 6;
147 }
148}
149
bsalomon@google.com9732f622012-01-31 15:19:21 +0000150struct DegenerateTestData {
151 DegenerateTestData() { fStage = kInitial; }
152 bool isDegenerate() const { return kNonDegenerate != fStage; }
153 enum {
154 kInitial,
155 kPoint,
156 kLine,
157 kNonDegenerate
158 } fStage;
159 GrPoint fFirstPoint;
160 GrVec fLineNormal;
161 GrScalar fLineC;
162};
163
164void update_degenerate_test(DegenerateTestData* data, const GrPoint& pt) {
165 static const SkScalar TOL = (SK_Scalar1 / 16);
166 static const SkScalar TOL_SQD = SkScalarMul(TOL, TOL);
167
168 switch (data->fStage) {
169 case DegenerateTestData::kInitial:
170 data->fFirstPoint = pt;
171 data->fStage = DegenerateTestData::kPoint;
172 break;
173 case DegenerateTestData::kPoint:
174 if (pt.distanceToSqd(data->fFirstPoint) > TOL_SQD) {
175 data->fLineNormal = pt - data->fFirstPoint;
176 data->fLineNormal.normalize();
177 data->fLineNormal.setOrthog(data->fLineNormal);
178 data->fLineC = -data->fLineNormal.dot(data->fFirstPoint);
179 data->fStage = DegenerateTestData::kLine;
180 }
181 break;
182 case DegenerateTestData::kLine:
183 if (SkScalarAbs(data->fLineNormal.dot(pt) + data->fLineC) > TOL) {
184 data->fStage = DegenerateTestData::kNonDegenerate;
185 }
186 case DegenerateTestData::kNonDegenerate:
187 break;
188 default:
189 GrCrash("Unexpected degenerate test stage.");
190 }
191}
192
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000193bool get_segments(const GrPath& path,
194 SegmentArray* segments,
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000195 SkPoint* fanPt,
196 int* vCount,
197 int* iCount) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000198 SkPath::Iter iter(path, true);
bsalomon@google.com5cc90d12012-01-17 16:28:34 +0000199 // This renderer overemphasises very thin path regions. We use the distance
200 // to the path from the sample to compute coverage. Every pixel intersected
201 // by the path will be hit and the maximum distance is sqrt(2)/2. We don't
202 // notice that the sample may be close to a very thin area of the path and
203 // thus should be very light. This is particularly egregious for degenerate
204 // line paths. We detect paths that are very close to a line (zero area) and
205 // draw nothing.
bsalomon@google.com9732f622012-01-31 15:19:21 +0000206 DegenerateTestData degenerateData;
207
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000208 for (;;) {
209 GrPoint pts[4];
210 GrPathCmd cmd = (GrPathCmd)iter.next(pts);
211 switch (cmd) {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000212 case kMove_PathCmd:
213 update_degenerate_test(&degenerateData, pts[0]);
214 break;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000215 case kLine_PathCmd: {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000216 update_degenerate_test(&degenerateData, pts[1]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000217 segments->push_back();
218 segments->back().fType = Segment::kLine;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000219 segments->back().fPts[0] = pts[1];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000220 break;
221 }
222 case kQuadratic_PathCmd:
bsalomon@google.com9732f622012-01-31 15:19:21 +0000223 update_degenerate_test(&degenerateData, pts[1]);
224 update_degenerate_test(&degenerateData, pts[2]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000225 segments->push_back();
226 segments->back().fType = Segment::kQuad;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000227 segments->back().fPts[0] = pts[1];
228 segments->back().fPts[1] = pts[2];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000229 break;
230 case kCubic_PathCmd: {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000231 update_degenerate_test(&degenerateData, pts[1]);
232 update_degenerate_test(&degenerateData, pts[2]);
233 update_degenerate_test(&degenerateData, pts[3]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000234 SkSTArray<15, SkPoint, true> quads;
235 GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, &quads);
236 int count = quads.count();
237 for (int q = 0; q < count; q += 3) {
238 segments->push_back();
239 segments->back().fType = Segment::kQuad;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000240 segments->back().fPts[0] = quads[q + 1];
241 segments->back().fPts[1] = quads[q + 2];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000242 }
243 break;
244 };
245 case kEnd_PathCmd:
bsalomon@google.com9732f622012-01-31 15:19:21 +0000246 if (degenerateData.isDegenerate()) {
247 return false;
248 } else {
bsalomon@google.com278dc692012-02-15 16:52:51 +0000249 SkPath::Direction dir;
250 GR_DEBUGCODE(bool succeeded = )
251 path.cheapComputeDirection(&dir);
252 GrAssert(succeeded);
253 compute_vectors(segments, fanPt, dir, vCount, iCount);
bsalomon@google.com9732f622012-01-31 15:19:21 +0000254 return true;
255 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000256 default:
257 break;
258 }
259 }
260}
261
262struct QuadVertex {
263 GrPoint fPos;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000264 GrPoint fUV;
265 GrScalar fD0;
266 GrScalar fD1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000267};
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000268
269void create_vertices(const SegmentArray& segments,
270 const SkPoint& fanPt,
271 QuadVertex* verts,
272 uint16_t* idxs) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000273 int v = 0;
274 int i = 0;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000275
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000276 int count = segments.count();
277 for (int a = 0; a < count; ++a) {
278 const Segment& sega = segments[a];
279 int b = (a + 1) % count;
280 const Segment& segb = segments[b];
281
282 // FIXME: These tris are inset in the 1 unit arc around the corner
283 verts[v + 0].fPos = sega.endPt();
284 verts[v + 1].fPos = verts[v + 0].fPos + sega.endNorm();
285 verts[v + 2].fPos = verts[v + 0].fPos + segb.fMid;
286 verts[v + 3].fPos = verts[v + 0].fPos + segb.fNorms[0];
287 verts[v + 0].fUV.set(0,0);
288 verts[v + 1].fUV.set(0,-SK_Scalar1);
289 verts[v + 2].fUV.set(0,-SK_Scalar1);
290 verts[v + 3].fUV.set(0,-SK_Scalar1);
291 verts[v + 0].fD0 = verts[v + 0].fD1 = -SK_Scalar1;
292 verts[v + 1].fD0 = verts[v + 1].fD1 = -SK_Scalar1;
293 verts[v + 2].fD0 = verts[v + 2].fD1 = -SK_Scalar1;
294 verts[v + 3].fD0 = verts[v + 3].fD1 = -SK_Scalar1;
295
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000296 idxs[i + 0] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000297 idxs[i + 1] = v + 2;
298 idxs[i + 2] = v + 1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000299 idxs[i + 3] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000300 idxs[i + 4] = v + 3;
301 idxs[i + 5] = v + 2;
302
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000303 v += 4;
304 i += 6;
305
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000306 if (Segment::kLine == segb.fType) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000307 verts[v + 0].fPos = fanPt;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000308 verts[v + 1].fPos = sega.endPt();
309 verts[v + 2].fPos = segb.fPts[0];
310
311 verts[v + 3].fPos = verts[v + 1].fPos + segb.fNorms[0];
312 verts[v + 4].fPos = verts[v + 2].fPos + segb.fNorms[0];
313
314 // we draw the line edge as a degenerate quad (u is 0, v is the
315 // signed distance to the edge)
316 GrScalar dist = fanPt.distanceToLineBetween(verts[v + 1].fPos,
317 verts[v + 2].fPos);
318 verts[v + 0].fUV.set(0, dist);
319 verts[v + 1].fUV.set(0, 0);
320 verts[v + 2].fUV.set(0, 0);
321 verts[v + 3].fUV.set(0, -SK_Scalar1);
322 verts[v + 4].fUV.set(0, -SK_Scalar1);
323
324 verts[v + 0].fD0 = verts[v + 0].fD1 = -SK_Scalar1;
325 verts[v + 1].fD0 = verts[v + 1].fD1 = -SK_Scalar1;
326 verts[v + 2].fD0 = verts[v + 2].fD1 = -SK_Scalar1;
327 verts[v + 3].fD0 = verts[v + 3].fD1 = -SK_Scalar1;
328 verts[v + 4].fD0 = verts[v + 4].fD1 = -SK_Scalar1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000329
330 idxs[i + 0] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000331 idxs[i + 1] = v + 2;
332 idxs[i + 2] = v + 1;
333
334 idxs[i + 3] = v + 3;
335 idxs[i + 4] = v + 1;
336 idxs[i + 5] = v + 2;
337
338 idxs[i + 6] = v + 4;
339 idxs[i + 7] = v + 3;
bsalomon@google.com06809612012-01-21 15:03:39 +0000340 idxs[i + 8] = v + 2;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000341
bsalomon@google.com06809612012-01-21 15:03:39 +0000342 v += 5;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000343 i += 9;
bsalomon@google.com06809612012-01-21 15:03:39 +0000344 } else {
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000345 GrPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]};
bsalomon@google.com495e2102012-01-21 14:48:36 +0000346
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000347 GrVec midVec = segb.fNorms[0] + segb.fNorms[1];
348 midVec.normalize();
bsalomon@google.com06809612012-01-21 15:03:39 +0000349
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000350 verts[v + 0].fPos = fanPt;
351 verts[v + 1].fPos = qpts[0];
352 verts[v + 2].fPos = qpts[2];
353 verts[v + 3].fPos = qpts[0] + segb.fNorms[0];
354 verts[v + 4].fPos = qpts[2] + segb.fNorms[1];
355 verts[v + 5].fPos = qpts[1] + midVec;
356
357 GrScalar c = segb.fNorms[0].dot(qpts[0]);
358 verts[v + 0].fD0 = -segb.fNorms[0].dot(fanPt) + c;
359 verts[v + 1].fD0 = 0.f;
360 verts[v + 2].fD0 = -segb.fNorms[0].dot(qpts[2]) + c;
361 verts[v + 3].fD0 = -GR_ScalarMax/100;
362 verts[v + 4].fD0 = -GR_ScalarMax/100;
363 verts[v + 5].fD0 = -GR_ScalarMax/100;
364
365 c = segb.fNorms[1].dot(qpts[2]);
366 verts[v + 0].fD1 = -segb.fNorms[1].dot(fanPt) + c;
367 verts[v + 1].fD1 = -segb.fNorms[1].dot(qpts[0]) + c;
368 verts[v + 2].fD1 = 0.f;
369 verts[v + 3].fD1 = -GR_ScalarMax/100;
370 verts[v + 4].fD1 = -GR_ScalarMax/100;
371 verts[v + 5].fD1 = -GR_ScalarMax/100;
372
bsalomon@google.com06809612012-01-21 15:03:39 +0000373 GrMatrix toUV;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000374 GrPathUtils::quadDesignSpaceToUVCoordsMatrix(qpts, &toUV);
375 toUV.mapPointsWithStride(&verts[v].fUV,
376 &verts[v].fPos,
377 sizeof(QuadVertex),
378 6);
bsalomon@google.com06809612012-01-21 15:03:39 +0000379
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000380 idxs[i + 0] = v + 3;
381 idxs[i + 1] = v + 1;
382 idxs[i + 2] = v + 2;
383 idxs[i + 3] = v + 4;
384 idxs[i + 4] = v + 3;
385 idxs[i + 5] = v + 2;
bsalomon@google.com06809612012-01-21 15:03:39 +0000386
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000387 idxs[i + 6] = v + 5;
388 idxs[i + 7] = v + 3;
389 idxs[i + 8] = v + 4;
390
391 idxs[i + 9] = v + 0;
392 idxs[i + 10] = v + 2;
393 idxs[i + 11] = v + 1;
394
395 v += 6;
396 i += 12;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000397 }
398 }
399}
400
401}
402
403void GrAAConvexPathRenderer::drawPath(GrDrawState::StageMask stageMask) {
404 GrAssert(fPath->isConvex());
405 if (fPath->isEmpty()) {
406 return;
407 }
408 GrDrawState* drawState = fTarget->drawState();
409
410 GrDrawTarget::AutoStateRestore asr;
411 GrMatrix vm = drawState->getViewMatrix();
412 vm.postTranslate(fTranslate.fX, fTranslate.fY);
413 asr.set(fTarget);
414 GrMatrix ivm;
415 if (vm.invert(&ivm)) {
416 drawState->preConcatSamplerMatrices(stageMask, ivm);
417 }
418 drawState->setViewMatrix(GrMatrix::I());
419
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000420 SkPath path;
421 fPath->transform(vm, &path);
422
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000423 GrVertexLayout layout = 0;
424 for (int s = 0; s < GrDrawState::kNumStages; ++s) {
425 if ((1 << s) & stageMask) {
426 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
427 }
428 }
429 layout |= GrDrawTarget::kEdge_VertexLayoutBit;
430
431 QuadVertex *verts;
432 uint16_t* idxs;
433
bsalomon@google.com06809612012-01-21 15:03:39 +0000434 int vCount;
435 int iCount;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000436 SegmentArray segments;
437 SkPoint fanPt;
438 if (!get_segments(path, &segments, &fanPt, &vCount, &iCount)) {
439 return;
440 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000441
442 if (!fTarget->reserveVertexSpace(layout,
443 vCount,
444 reinterpret_cast<void**>(&verts))) {
445 return;
446 }
447 if (!fTarget->reserveIndexSpace(iCount, reinterpret_cast<void**>(&idxs))) {
448 fTarget->resetVertexSource();
449 return;
450 }
451
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000452 create_vertices(segments, fanPt, verts, idxs);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000453
454 drawState->setVertexEdgeType(GrDrawState::kQuad_EdgeType);
455 fTarget->drawIndexed(kTriangles_PrimitiveType,
456 0, // start vertex
457 0, // start index
458 vCount,
459 iCount);
460}
461