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