1
2 /*
3 * Copyright 2013 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 // This test only works with the GPU backend.
10
11 #include "gm.h"
12
13 #if SK_SUPPORT_GPU
14
15 #include "GrBatchTarget.h"
16 #include "GrContext.h"
17 #include "GrPathUtils.h"
18 #include "GrTest.h"
19 #include "GrTestBatch.h"
20 #include "SkColorPriv.h"
21 #include "SkDevice.h"
22 #include "SkGeometry.h"
23
24 #include "effects/GrBezierEffect.h"
25
eval_line(const SkPoint & p,const SkScalar lineEq[3],SkScalar sign)26 static inline SkScalar eval_line(const SkPoint& p, const SkScalar lineEq[3], SkScalar sign) {
27 return sign * (lineEq[0] * p.fX + lineEq[1] * p.fY + lineEq[2]);
28 }
29
30 namespace skiagm {
31
32 class BezierCubicOrConicTestBatch : public GrTestBatch {
33 public:
34 struct Geometry : public GrTestBatch::Geometry {
35 SkRect fBounds;
36 };
37
name() const38 const char* name() const override { return "BezierCubicOrConicTestBatch"; }
39
Create(const GrGeometryProcessor * gp,const Geometry & geo,const SkScalar klmEqs[9],SkScalar sign)40 static GrBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo,
41 const SkScalar klmEqs[9], SkScalar sign) {
42 return SkNEW_ARGS(BezierCubicOrConicTestBatch, (gp, geo, klmEqs, sign));
43 }
44
45 private:
BezierCubicOrConicTestBatch(const GrGeometryProcessor * gp,const Geometry & geo,const SkScalar klmEqs[9],SkScalar sign)46 BezierCubicOrConicTestBatch(const GrGeometryProcessor* gp, const Geometry& geo,
47 const SkScalar klmEqs[9], SkScalar sign)
48 : INHERITED(gp, geo.fBounds) {
49 for (int i = 0; i < 9; i++) {
50 fKlmEqs[i] = klmEqs[i];
51 }
52
53 fGeometry = geo;
54 fSign = sign;
55 }
56
57 struct Vertex {
58 SkPoint fPosition;
59 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
60 };
61
geoData(int index)62 Geometry* geoData(int index) override {
63 SkASSERT(0 == index);
64 return &fGeometry;
65 }
66
geoData(int index) const67 const Geometry* geoData(int index) const override {
68 SkASSERT(0 == index);
69 return &fGeometry;
70 }
71
onGenerateGeometry(GrBatchTarget * batchTarget,const GrPipeline * pipeline)72 void onGenerateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
73 QuadHelper helper;
74 size_t vertexStride = this->geometryProcessor()->getVertexStride();
75 SkASSERT(vertexStride == sizeof(Vertex));
76 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(batchTarget, vertexStride, 1));
77 if (!verts) {
78 return;
79 }
80
81 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop,
82 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom,
83 sizeof(Vertex));
84 for (int v = 0; v < 4; ++v) {
85 verts[v].fKLM[0] = eval_line(verts[v].fPosition, fKlmEqs + 0, fSign);
86 verts[v].fKLM[1] = eval_line(verts[v].fPosition, fKlmEqs + 3, fSign);
87 verts[v].fKLM[2] = eval_line(verts[v].fPosition, fKlmEqs + 6, 1.f);
88 }
89 helper.issueDraw(batchTarget);
90 }
91
92 Geometry fGeometry;
93 SkScalar fKlmEqs[9];
94 SkScalar fSign;
95
96 static const int kVertsPerCubic = 4;
97 static const int kIndicesPerCubic = 6;
98
99 typedef GrTestBatch INHERITED;
100 };
101
102 /**
103 * This GM directly exercises effects that draw Bezier curves in the GPU backend.
104 */
105 class BezierCubicEffects : public GM {
106 public:
BezierCubicEffects()107 BezierCubicEffects() {
108 this->setBGColor(0xFFFFFFFF);
109 }
110
111 protected:
onShortName()112 SkString onShortName() override {
113 return SkString("bezier_cubic_effects");
114 }
115
onISize()116 SkISize onISize() override {
117 return SkISize::Make(800, 800);
118 }
119
onDraw(SkCanvas * canvas)120 void onDraw(SkCanvas* canvas) override {
121 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget();
122 if (NULL == rt) {
123 this->drawGpuOnlyMessage(canvas);
124 return;
125 }
126 GrContext* context = rt->getContext();
127 if (NULL == context) {
128 return;
129 }
130
131 struct Vertex {
132 SkPoint fPosition;
133 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
134 };
135
136 static const int kNumCubics = 15;
137 SkRandom rand;
138
139 // Mult by 3 for each edge effect type
140 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumCubics*3)));
141 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumCubics*3) / numCols);
142 SkScalar w = SkIntToScalar(rt->width()) / numCols;
143 SkScalar h = SkIntToScalar(rt->height()) / numRows;
144 int row = 0;
145 int col = 0;
146 static const GrColor color = 0xff000000;
147
148 for (int i = 0; i < kNumCubics; ++i) {
149 SkPoint baseControlPts[] = {
150 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
151 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
152 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
153 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}
154 };
155 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) {
156 SkAutoTUnref<GrGeometryProcessor> gp;
157 { // scope to contain GrTestTarget
158 GrTestTarget tt;
159 context->getTestTarget(&tt);
160 if (NULL == tt.target()) {
161 continue;
162 }
163 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType;
164 gp.reset(GrCubicEffect::Create(color, SkMatrix::I(), et,
165 *tt.target()->caps()));
166 if (!gp) {
167 continue;
168 }
169 }
170
171 SkScalar x = SkScalarMul(col, w);
172 SkScalar y = SkScalarMul(row, h);
173 SkPoint controlPts[] = {
174 {x + baseControlPts[0].fX, y + baseControlPts[0].fY},
175 {x + baseControlPts[1].fX, y + baseControlPts[1].fY},
176 {x + baseControlPts[2].fX, y + baseControlPts[2].fY},
177 {x + baseControlPts[3].fX, y + baseControlPts[3].fY}
178 };
179 SkPoint chopped[10];
180 SkScalar klmEqs[9];
181 SkScalar klmSigns[3];
182 int cnt = GrPathUtils::chopCubicAtLoopIntersection(controlPts,
183 chopped,
184 klmEqs,
185 klmSigns);
186
187 SkPaint ctrlPtPaint;
188 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
189 for (int i = 0; i < 4; ++i) {
190 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint);
191 }
192
193 SkPaint polyPaint;
194 polyPaint.setColor(0xffA0A0A0);
195 polyPaint.setStrokeWidth(0);
196 polyPaint.setStyle(SkPaint::kStroke_Style);
197 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, controlPts, polyPaint);
198
199 SkPaint choppedPtPaint;
200 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
201
202 for (int c = 0; c < cnt; ++c) {
203 SkPoint* pts = chopped + 3 * c;
204
205 for (int i = 0; i < 4; ++i) {
206 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint);
207 }
208
209 SkRect bounds;
210 bounds.set(pts, 4);
211
212 SkPaint boundsPaint;
213 boundsPaint.setColor(0xff808080);
214 boundsPaint.setStrokeWidth(0);
215 boundsPaint.setStyle(SkPaint::kStroke_Style);
216 canvas->drawRect(bounds, boundsPaint);
217
218 GrTestTarget tt;
219 context->getTestTarget(&tt);
220 SkASSERT(tt.target());
221
222 GrPipelineBuilder pipelineBuilder;
223 pipelineBuilder.setRenderTarget(rt);
224
225 BezierCubicOrConicTestBatch::Geometry geometry;
226 geometry.fColor = color;
227 geometry.fBounds = bounds;
228
229 SkAutoTUnref<GrBatch> batch(
230 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, klmSigns[c]));
231
232 tt.target()->drawBatch(&pipelineBuilder, batch);
233 }
234 ++col;
235 if (numCols == col) {
236 col = 0;
237 ++row;
238 }
239 }
240 }
241 }
242
243 private:
244 typedef GM INHERITED;
245 };
246
247 //////////////////////////////////////////////////////////////////////////////
248
249 /**
250 * This GM directly exercises effects that draw Bezier curves in the GPU backend.
251 */
252 class BezierConicEffects : public GM {
253 public:
BezierConicEffects()254 BezierConicEffects() {
255 this->setBGColor(0xFFFFFFFF);
256 }
257
258 protected:
onShortName()259 SkString onShortName() override {
260 return SkString("bezier_conic_effects");
261 }
262
onISize()263 SkISize onISize() override {
264 return SkISize::Make(800, 800);
265 }
266
267
onDraw(SkCanvas * canvas)268 void onDraw(SkCanvas* canvas) override {
269 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget();
270 if (NULL == rt) {
271 this->drawGpuOnlyMessage(canvas);
272 return;
273 }
274 GrContext* context = rt->getContext();
275 if (NULL == context) {
276 return;
277 }
278
279 struct Vertex {
280 SkPoint fPosition;
281 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
282 };
283
284 static const int kNumConics = 10;
285 SkRandom rand;
286
287 // Mult by 3 for each edge effect type
288 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumConics*3)));
289 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumConics*3) / numCols);
290 SkScalar w = SkIntToScalar(rt->width()) / numCols;
291 SkScalar h = SkIntToScalar(rt->height()) / numRows;
292 int row = 0;
293 int col = 0;
294 static const GrColor color = 0xff000000;
295
296 for (int i = 0; i < kNumConics; ++i) {
297 SkPoint baseControlPts[] = {
298 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
299 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
300 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}
301 };
302 SkScalar weight = rand.nextRangeF(0.f, 2.f);
303 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) {
304 SkAutoTUnref<GrGeometryProcessor> gp;
305 { // scope to contain GrTestTarget
306 GrTestTarget tt;
307 context->getTestTarget(&tt);
308 if (NULL == tt.target()) {
309 continue;
310 }
311 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType;
312 gp.reset(GrConicEffect::Create(color, SkMatrix::I(), et,
313 *tt.target()->caps(), SkMatrix::I()));
314 if (!gp) {
315 continue;
316 }
317 }
318
319 SkScalar x = SkScalarMul(col, w);
320 SkScalar y = SkScalarMul(row, h);
321 SkPoint controlPts[] = {
322 {x + baseControlPts[0].fX, y + baseControlPts[0].fY},
323 {x + baseControlPts[1].fX, y + baseControlPts[1].fY},
324 {x + baseControlPts[2].fX, y + baseControlPts[2].fY}
325 };
326 SkConic dst[4];
327 SkScalar klmEqs[9];
328 int cnt = chop_conic(controlPts, dst, weight);
329 GrPathUtils::getConicKLM(controlPts, weight, klmEqs);
330
331 SkPaint ctrlPtPaint;
332 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
333 for (int i = 0; i < 3; ++i) {
334 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint);
335 }
336
337 SkPaint polyPaint;
338 polyPaint.setColor(0xffA0A0A0);
339 polyPaint.setStrokeWidth(0);
340 polyPaint.setStyle(SkPaint::kStroke_Style);
341 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint);
342
343 SkPaint choppedPtPaint;
344 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
345
346 for (int c = 0; c < cnt; ++c) {
347 SkPoint* pts = dst[c].fPts;
348 for (int i = 0; i < 3; ++i) {
349 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint);
350 }
351
352 SkRect bounds;
353 //SkPoint bPts[] = {{0.f, 0.f}, {800.f, 800.f}};
354 //bounds.set(bPts, 2);
355 bounds.set(pts, 3);
356
357 SkPaint boundsPaint;
358 boundsPaint.setColor(0xff808080);
359 boundsPaint.setStrokeWidth(0);
360 boundsPaint.setStyle(SkPaint::kStroke_Style);
361 canvas->drawRect(bounds, boundsPaint);
362
363 GrTestTarget tt;
364 context->getTestTarget(&tt);
365 SkASSERT(tt.target());
366
367 GrPipelineBuilder pipelineBuilder;
368 pipelineBuilder.setRenderTarget(rt);
369
370 BezierCubicOrConicTestBatch::Geometry geometry;
371 geometry.fColor = color;
372 geometry.fBounds = bounds;
373
374 SkAutoTUnref<GrBatch> batch(
375 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, 1.f));
376
377 tt.target()->drawBatch(&pipelineBuilder, batch);
378 }
379 ++col;
380 if (numCols == col) {
381 col = 0;
382 ++row;
383 }
384 }
385 }
386 }
387
388 private:
389 // Uses the max curvature function for quads to estimate
390 // where to chop the conic. If the max curvature is not
391 // found along the curve segment it will return 1 and
392 // dst[0] is the original conic. If it returns 2 the dst[0]
393 // and dst[1] are the two new conics.
split_conic(const SkPoint src[3],SkConic dst[2],const SkScalar weight)394 int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
395 SkScalar t = SkFindQuadMaxCurvature(src);
396 if (t == 0) {
397 if (dst) {
398 dst[0].set(src, weight);
399 }
400 return 1;
401 } else {
402 if (dst) {
403 SkConic conic;
404 conic.set(src, weight);
405 conic.chopAt(t, dst);
406 }
407 return 2;
408 }
409 }
410
411 // Calls split_conic on the entire conic and then once more on each subsection.
412 // Most cases will result in either 1 conic (chop point is not within t range)
413 // or 3 points (split once and then one subsection is split again).
chop_conic(const SkPoint src[3],SkConic dst[4],const SkScalar weight)414 int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
415 SkConic dstTemp[2];
416 int conicCnt = split_conic(src, dstTemp, weight);
417 if (2 == conicCnt) {
418 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
419 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
420 } else {
421 dst[0] = dstTemp[0];
422 }
423 return conicCnt;
424 }
425
426 typedef GM INHERITED;
427 };
428
429 //////////////////////////////////////////////////////////////////////////////
430
431 class BezierQuadTestBatch : public GrTestBatch {
432 public:
433 struct Geometry : public GrTestBatch::Geometry {
434 SkRect fBounds;
435 };
436
name() const437 const char* name() const override { return "BezierQuadTestBatch"; }
438
Create(const GrGeometryProcessor * gp,const Geometry & geo,const GrPathUtils::QuadUVMatrix & devToUV)439 static GrBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo,
440 const GrPathUtils::QuadUVMatrix& devToUV) {
441 return SkNEW_ARGS(BezierQuadTestBatch, (gp, geo, devToUV));
442 }
443
444 private:
BezierQuadTestBatch(const GrGeometryProcessor * gp,const Geometry & geo,const GrPathUtils::QuadUVMatrix & devToUV)445 BezierQuadTestBatch(const GrGeometryProcessor* gp, const Geometry& geo,
446 const GrPathUtils::QuadUVMatrix& devToUV)
447 : INHERITED(gp, geo.fBounds)
448 , fGeometry(geo)
449 , fDevToUV(devToUV) {
450 }
451
452 struct Vertex {
453 SkPoint fPosition;
454 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
455 };
456
geoData(int index)457 Geometry* geoData(int index) override {
458 SkASSERT(0 == index);
459 return &fGeometry;
460 }
461
geoData(int index) const462 const Geometry* geoData(int index) const override {
463 SkASSERT(0 == index);
464 return &fGeometry;
465 }
466
onGenerateGeometry(GrBatchTarget * batchTarget,const GrPipeline * pipeline)467 void onGenerateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
468 QuadHelper helper;
469 size_t vertexStride = this->geometryProcessor()->getVertexStride();
470 SkASSERT(vertexStride == sizeof(Vertex));
471 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(batchTarget, vertexStride, 1));
472 if (!verts) {
473 return;
474 }
475 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop,
476 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom,
477 sizeof(Vertex));
478 fDevToUV.apply<4, sizeof(Vertex), sizeof(SkPoint)>(verts);
479 helper.issueDraw(batchTarget);
480 }
481
482 Geometry fGeometry;
483 GrPathUtils::QuadUVMatrix fDevToUV;
484
485 static const int kVertsPerCubic = 4;
486 static const int kIndicesPerCubic = 6;
487
488 typedef GrTestBatch INHERITED;
489 };
490
491 /**
492 * This GM directly exercises effects that draw Bezier quad curves in the GPU backend.
493 */
494 class BezierQuadEffects : public GM {
495 public:
BezierQuadEffects()496 BezierQuadEffects() {
497 this->setBGColor(0xFFFFFFFF);
498 }
499
500 protected:
onShortName()501 SkString onShortName() override {
502 return SkString("bezier_quad_effects");
503 }
504
onISize()505 SkISize onISize() override {
506 return SkISize::Make(800, 800);
507 }
508
509
onDraw(SkCanvas * canvas)510 void onDraw(SkCanvas* canvas) override {
511 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget();
512 if (NULL == rt) {
513 this->drawGpuOnlyMessage(canvas);
514 return;
515 }
516 GrContext* context = rt->getContext();
517 if (NULL == context) {
518 return;
519 }
520
521 struct Vertex {
522 SkPoint fPosition;
523 float fUV[4]; // The last two values are ignored. The effect expects a vec4f.
524 };
525
526 static const int kNumQuads = 5;
527 SkRandom rand;
528
529 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumQuads*3)));
530 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumQuads*3) / numCols);
531 SkScalar w = SkIntToScalar(rt->width()) / numCols;
532 SkScalar h = SkIntToScalar(rt->height()) / numRows;
533 int row = 0;
534 int col = 0;
535 static const GrColor color = 0xff000000;
536
537 for (int i = 0; i < kNumQuads; ++i) {
538 SkPoint baseControlPts[] = {
539 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
540 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
541 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}
542 };
543 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) {
544 SkAutoTUnref<GrGeometryProcessor> gp;
545 { // scope to contain GrTestTarget
546 GrTestTarget tt;
547 context->getTestTarget(&tt);
548 if (NULL == tt.target()) {
549 continue;
550 }
551 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType;
552 gp.reset(GrQuadEffect::Create(color, SkMatrix::I(), et,
553 *tt.target()->caps(), SkMatrix::I()));
554 if (!gp) {
555 continue;
556 }
557 }
558
559 SkScalar x = SkScalarMul(col, w);
560 SkScalar y = SkScalarMul(row, h);
561 SkPoint controlPts[] = {
562 {x + baseControlPts[0].fX, y + baseControlPts[0].fY},
563 {x + baseControlPts[1].fX, y + baseControlPts[1].fY},
564 {x + baseControlPts[2].fX, y + baseControlPts[2].fY}
565 };
566 SkPoint chopped[5];
567 int cnt = SkChopQuadAtMaxCurvature(controlPts, chopped);
568
569 SkPaint ctrlPtPaint;
570 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
571 for (int i = 0; i < 3; ++i) {
572 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint);
573 }
574
575 SkPaint polyPaint;
576 polyPaint.setColor(0xffA0A0A0);
577 polyPaint.setStrokeWidth(0);
578 polyPaint.setStyle(SkPaint::kStroke_Style);
579 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint);
580
581 SkPaint choppedPtPaint;
582 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
583
584 for (int c = 0; c < cnt; ++c) {
585 SkPoint* pts = chopped + 2 * c;
586
587 for (int i = 0; i < 3; ++i) {
588 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint);
589 }
590
591 SkRect bounds;
592 bounds.set(pts, 3);
593
594 SkPaint boundsPaint;
595 boundsPaint.setColor(0xff808080);
596 boundsPaint.setStrokeWidth(0);
597 boundsPaint.setStyle(SkPaint::kStroke_Style);
598 canvas->drawRect(bounds, boundsPaint);
599
600 GrTestTarget tt;
601 context->getTestTarget(&tt);
602 SkASSERT(tt.target());
603
604 GrPipelineBuilder pipelineBuilder;
605 pipelineBuilder.setRenderTarget(rt);
606
607 GrPathUtils::QuadUVMatrix DevToUV(pts);
608
609 BezierQuadTestBatch::Geometry geometry;
610 geometry.fColor = color;
611 geometry.fBounds = bounds;
612
613 SkAutoTUnref<GrBatch> batch(BezierQuadTestBatch::Create(gp, geometry, DevToUV));
614
615 tt.target()->drawBatch(&pipelineBuilder, batch);
616 }
617 ++col;
618 if (numCols == col) {
619 col = 0;
620 ++row;
621 }
622 }
623 }
624 }
625
626 private:
627 typedef GM INHERITED;
628 };
629
630 DEF_GM( return SkNEW(BezierCubicEffects); )
631 DEF_GM( return SkNEW(BezierConicEffects); )
632 DEF_GM( return SkNEW(BezierQuadEffects); )
633
634 }
635
636 #endif
637