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