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