blob: e1dbb63380985d39df54f1663441c05df160944e [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 }
72 area *= 3;
73 area = GrScalarDiv(GR_Scalar1, area);
74 center.fX = GrScalarMul(center.fX, area);
75 center.fY = GrScalarMul(center.fY, area);
76 *c = center;
77}
78
79void compute_vectors(SegmentArray* segments,
80 SkPoint* fanPt,
81 int* vCount,
82 int* iCount) {
83 center_of_mass(*segments, fanPt);
84 int count = segments->count();
85
86 // figure out which way the normals should point
87 GrPoint::Side normSide;
88 fanPt->distanceToLineBetweenSqd((*segments)[0].endPt(),
89 (*segments)[1].endPt(),
90 &normSide);
91
92 *vCount = 0;
93 *iCount = 0;
94 // compute normals at all points
95 for (int a = 0; a < count; ++a) {
96 const Segment& sega = (*segments)[a];
97 int b = (a + 1) % count;
98 Segment& segb = (*segments)[b];
99
100 const GrPoint* prevPt = &sega.endPt();
101 int n = segb.countPoints();
102 for (int p = 0; p < n; ++p) {
103 segb.fNorms[p] = segb.fPts[p] - *prevPt;
104 segb.fNorms[p].normalize();
105 segb.fNorms[p].setOrthog(segb.fNorms[p], normSide);
106 prevPt = &segb.fPts[p];
107 }
108 if (Segment::kLine == segb.fType) {
109 *vCount += 5;
110 *iCount += 9;
111 } else {
112 *vCount += 6;
113 *iCount += 12;
114 }
115 }
116
117 // compute mid-vectors where segments meet. TODO: Detect shallow corners
118 // and leave out the wedges and close gaps by stitching segments together.
119 for (int a = 0; a < count; ++a) {
120 const Segment& sega = (*segments)[a];
121 int b = (a + 1) % count;
122 Segment& segb = (*segments)[b];
123 segb.fMid = segb.fNorms[0] + sega.endNorm();
124 segb.fMid.normalize();
125 // corner wedges
126 *vCount += 4;
127 *iCount += 6;
128 }
129}
130
bsalomon@google.com9732f622012-01-31 15:19:21 +0000131struct DegenerateTestData {
132 DegenerateTestData() { fStage = kInitial; }
133 bool isDegenerate() const { return kNonDegenerate != fStage; }
134 enum {
135 kInitial,
136 kPoint,
137 kLine,
138 kNonDegenerate
139 } fStage;
140 GrPoint fFirstPoint;
141 GrVec fLineNormal;
142 GrScalar fLineC;
143};
144
145void update_degenerate_test(DegenerateTestData* data, const GrPoint& pt) {
146 static const SkScalar TOL = (SK_Scalar1 / 16);
147 static const SkScalar TOL_SQD = SkScalarMul(TOL, TOL);
148
149 switch (data->fStage) {
150 case DegenerateTestData::kInitial:
151 data->fFirstPoint = pt;
152 data->fStage = DegenerateTestData::kPoint;
153 break;
154 case DegenerateTestData::kPoint:
155 if (pt.distanceToSqd(data->fFirstPoint) > TOL_SQD) {
156 data->fLineNormal = pt - data->fFirstPoint;
157 data->fLineNormal.normalize();
158 data->fLineNormal.setOrthog(data->fLineNormal);
159 data->fLineC = -data->fLineNormal.dot(data->fFirstPoint);
160 data->fStage = DegenerateTestData::kLine;
161 }
162 break;
163 case DegenerateTestData::kLine:
164 if (SkScalarAbs(data->fLineNormal.dot(pt) + data->fLineC) > TOL) {
165 data->fStage = DegenerateTestData::kNonDegenerate;
166 }
167 case DegenerateTestData::kNonDegenerate:
168 break;
169 default:
170 GrCrash("Unexpected degenerate test stage.");
171 }
172}
173
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000174bool get_segments(const GrPath& path,
175 SegmentArray* segments,
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000176 SkPoint* fanPt,
177 int* vCount,
178 int* iCount) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000179 SkPath::Iter iter(path, true);
bsalomon@google.com5cc90d12012-01-17 16:28:34 +0000180 // This renderer overemphasises very thin path regions. We use the distance
181 // to the path from the sample to compute coverage. Every pixel intersected
182 // by the path will be hit and the maximum distance is sqrt(2)/2. We don't
183 // notice that the sample may be close to a very thin area of the path and
184 // thus should be very light. This is particularly egregious for degenerate
185 // line paths. We detect paths that are very close to a line (zero area) and
186 // draw nothing.
bsalomon@google.com9732f622012-01-31 15:19:21 +0000187 DegenerateTestData degenerateData;
188
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000189 for (;;) {
190 GrPoint pts[4];
191 GrPathCmd cmd = (GrPathCmd)iter.next(pts);
192 switch (cmd) {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000193 case kMove_PathCmd:
194 update_degenerate_test(&degenerateData, pts[0]);
195 break;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000196 case kLine_PathCmd: {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000197 update_degenerate_test(&degenerateData, pts[1]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000198 segments->push_back();
199 segments->back().fType = Segment::kLine;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000200 segments->back().fPts[0] = pts[1];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000201 break;
202 }
203 case kQuadratic_PathCmd:
bsalomon@google.com9732f622012-01-31 15:19:21 +0000204 update_degenerate_test(&degenerateData, pts[1]);
205 update_degenerate_test(&degenerateData, pts[2]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000206 segments->push_back();
207 segments->back().fType = Segment::kQuad;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000208 segments->back().fPts[0] = pts[1];
209 segments->back().fPts[1] = pts[2];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000210 break;
211 case kCubic_PathCmd: {
bsalomon@google.com9732f622012-01-31 15:19:21 +0000212 update_degenerate_test(&degenerateData, pts[1]);
213 update_degenerate_test(&degenerateData, pts[2]);
214 update_degenerate_test(&degenerateData, pts[3]);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000215 SkSTArray<15, SkPoint, true> quads;
216 GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, &quads);
217 int count = quads.count();
218 for (int q = 0; q < count; q += 3) {
219 segments->push_back();
220 segments->back().fType = Segment::kQuad;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000221 segments->back().fPts[0] = quads[q + 1];
222 segments->back().fPts[1] = quads[q + 2];
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000223 }
224 break;
225 };
226 case kEnd_PathCmd:
bsalomon@google.com9732f622012-01-31 15:19:21 +0000227 if (degenerateData.isDegenerate()) {
228 return false;
229 } else {
230 compute_vectors(segments, fanPt, vCount, iCount);
231 return true;
232 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000233 default:
234 break;
235 }
236 }
237}
238
239struct QuadVertex {
240 GrPoint fPos;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000241 GrPoint fUV;
242 GrScalar fD0;
243 GrScalar fD1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000244};
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000245
246void create_vertices(const SegmentArray& segments,
247 const SkPoint& fanPt,
248 QuadVertex* verts,
249 uint16_t* idxs) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000250 int v = 0;
251 int i = 0;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000252
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000253 int count = segments.count();
254 for (int a = 0; a < count; ++a) {
255 const Segment& sega = segments[a];
256 int b = (a + 1) % count;
257 const Segment& segb = segments[b];
258
259 // FIXME: These tris are inset in the 1 unit arc around the corner
260 verts[v + 0].fPos = sega.endPt();
261 verts[v + 1].fPos = verts[v + 0].fPos + sega.endNorm();
262 verts[v + 2].fPos = verts[v + 0].fPos + segb.fMid;
263 verts[v + 3].fPos = verts[v + 0].fPos + segb.fNorms[0];
264 verts[v + 0].fUV.set(0,0);
265 verts[v + 1].fUV.set(0,-SK_Scalar1);
266 verts[v + 2].fUV.set(0,-SK_Scalar1);
267 verts[v + 3].fUV.set(0,-SK_Scalar1);
268 verts[v + 0].fD0 = verts[v + 0].fD1 = -SK_Scalar1;
269 verts[v + 1].fD0 = verts[v + 1].fD1 = -SK_Scalar1;
270 verts[v + 2].fD0 = verts[v + 2].fD1 = -SK_Scalar1;
271 verts[v + 3].fD0 = verts[v + 3].fD1 = -SK_Scalar1;
272
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000273 idxs[i + 0] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000274 idxs[i + 1] = v + 2;
275 idxs[i + 2] = v + 1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000276 idxs[i + 3] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000277 idxs[i + 4] = v + 3;
278 idxs[i + 5] = v + 2;
279
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000280 v += 4;
281 i += 6;
282
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000283 if (Segment::kLine == segb.fType) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000284 verts[v + 0].fPos = fanPt;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000285 verts[v + 1].fPos = sega.endPt();
286 verts[v + 2].fPos = segb.fPts[0];
287
288 verts[v + 3].fPos = verts[v + 1].fPos + segb.fNorms[0];
289 verts[v + 4].fPos = verts[v + 2].fPos + segb.fNorms[0];
290
291 // we draw the line edge as a degenerate quad (u is 0, v is the
292 // signed distance to the edge)
293 GrScalar dist = fanPt.distanceToLineBetween(verts[v + 1].fPos,
294 verts[v + 2].fPos);
295 verts[v + 0].fUV.set(0, dist);
296 verts[v + 1].fUV.set(0, 0);
297 verts[v + 2].fUV.set(0, 0);
298 verts[v + 3].fUV.set(0, -SK_Scalar1);
299 verts[v + 4].fUV.set(0, -SK_Scalar1);
300
301 verts[v + 0].fD0 = verts[v + 0].fD1 = -SK_Scalar1;
302 verts[v + 1].fD0 = verts[v + 1].fD1 = -SK_Scalar1;
303 verts[v + 2].fD0 = verts[v + 2].fD1 = -SK_Scalar1;
304 verts[v + 3].fD0 = verts[v + 3].fD1 = -SK_Scalar1;
305 verts[v + 4].fD0 = verts[v + 4].fD1 = -SK_Scalar1;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000306
307 idxs[i + 0] = v + 0;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000308 idxs[i + 1] = v + 2;
309 idxs[i + 2] = v + 1;
310
311 idxs[i + 3] = v + 3;
312 idxs[i + 4] = v + 1;
313 idxs[i + 5] = v + 2;
314
315 idxs[i + 6] = v + 4;
316 idxs[i + 7] = v + 3;
bsalomon@google.com06809612012-01-21 15:03:39 +0000317 idxs[i + 8] = v + 2;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000318
bsalomon@google.com06809612012-01-21 15:03:39 +0000319 v += 5;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000320 i += 9;
bsalomon@google.com06809612012-01-21 15:03:39 +0000321 } else {
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000322 GrPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]};
bsalomon@google.com495e2102012-01-21 14:48:36 +0000323
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000324 GrVec midVec = segb.fNorms[0] + segb.fNorms[1];
325 midVec.normalize();
bsalomon@google.com06809612012-01-21 15:03:39 +0000326
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000327 verts[v + 0].fPos = fanPt;
328 verts[v + 1].fPos = qpts[0];
329 verts[v + 2].fPos = qpts[2];
330 verts[v + 3].fPos = qpts[0] + segb.fNorms[0];
331 verts[v + 4].fPos = qpts[2] + segb.fNorms[1];
332 verts[v + 5].fPos = qpts[1] + midVec;
333
334 GrScalar c = segb.fNorms[0].dot(qpts[0]);
335 verts[v + 0].fD0 = -segb.fNorms[0].dot(fanPt) + c;
336 verts[v + 1].fD0 = 0.f;
337 verts[v + 2].fD0 = -segb.fNorms[0].dot(qpts[2]) + c;
338 verts[v + 3].fD0 = -GR_ScalarMax/100;
339 verts[v + 4].fD0 = -GR_ScalarMax/100;
340 verts[v + 5].fD0 = -GR_ScalarMax/100;
341
342 c = segb.fNorms[1].dot(qpts[2]);
343 verts[v + 0].fD1 = -segb.fNorms[1].dot(fanPt) + c;
344 verts[v + 1].fD1 = -segb.fNorms[1].dot(qpts[0]) + c;
345 verts[v + 2].fD1 = 0.f;
346 verts[v + 3].fD1 = -GR_ScalarMax/100;
347 verts[v + 4].fD1 = -GR_ScalarMax/100;
348 verts[v + 5].fD1 = -GR_ScalarMax/100;
349
bsalomon@google.com06809612012-01-21 15:03:39 +0000350 GrMatrix toUV;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000351 GrPathUtils::quadDesignSpaceToUVCoordsMatrix(qpts, &toUV);
352 toUV.mapPointsWithStride(&verts[v].fUV,
353 &verts[v].fPos,
354 sizeof(QuadVertex),
355 6);
bsalomon@google.com06809612012-01-21 15:03:39 +0000356
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000357 idxs[i + 0] = v + 3;
358 idxs[i + 1] = v + 1;
359 idxs[i + 2] = v + 2;
360 idxs[i + 3] = v + 4;
361 idxs[i + 4] = v + 3;
362 idxs[i + 5] = v + 2;
bsalomon@google.com06809612012-01-21 15:03:39 +0000363
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000364 idxs[i + 6] = v + 5;
365 idxs[i + 7] = v + 3;
366 idxs[i + 8] = v + 4;
367
368 idxs[i + 9] = v + 0;
369 idxs[i + 10] = v + 2;
370 idxs[i + 11] = v + 1;
371
372 v += 6;
373 i += 12;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000374 }
375 }
376}
377
378}
379
380void GrAAConvexPathRenderer::drawPath(GrDrawState::StageMask stageMask) {
381 GrAssert(fPath->isConvex());
382 if (fPath->isEmpty()) {
383 return;
384 }
385 GrDrawState* drawState = fTarget->drawState();
386
387 GrDrawTarget::AutoStateRestore asr;
388 GrMatrix vm = drawState->getViewMatrix();
389 vm.postTranslate(fTranslate.fX, fTranslate.fY);
390 asr.set(fTarget);
391 GrMatrix ivm;
392 if (vm.invert(&ivm)) {
393 drawState->preConcatSamplerMatrices(stageMask, ivm);
394 }
395 drawState->setViewMatrix(GrMatrix::I());
396
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000397 SkPath path;
398 fPath->transform(vm, &path);
399
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000400 GrVertexLayout layout = 0;
401 for (int s = 0; s < GrDrawState::kNumStages; ++s) {
402 if ((1 << s) & stageMask) {
403 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
404 }
405 }
406 layout |= GrDrawTarget::kEdge_VertexLayoutBit;
407
408 QuadVertex *verts;
409 uint16_t* idxs;
410
bsalomon@google.com06809612012-01-21 15:03:39 +0000411 int vCount;
412 int iCount;
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000413 SegmentArray segments;
414 SkPoint fanPt;
415 if (!get_segments(path, &segments, &fanPt, &vCount, &iCount)) {
416 return;
417 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000418
419 if (!fTarget->reserveVertexSpace(layout,
420 vCount,
421 reinterpret_cast<void**>(&verts))) {
422 return;
423 }
424 if (!fTarget->reserveIndexSpace(iCount, reinterpret_cast<void**>(&idxs))) {
425 fTarget->resetVertexSource();
426 return;
427 }
428
bsalomon@google.com9aed1142012-01-30 14:28:39 +0000429 create_vertices(segments, fanPt, verts, idxs);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000430
431 drawState->setVertexEdgeType(GrDrawState::kQuad_EdgeType);
432 fTarget->drawIndexed(kTriangles_PrimitiveType,
433 0, // start vertex
434 0, // start index
435 vCount,
436 iCount);
437}
438