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