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