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#include "src/gpu/ops/GrOvalOpFactory.h"
9
10#include "include/core/SkStrokeRec.h"
11#include "src/core/SkMatrixPriv.h"
12#include "src/core/SkRRectPriv.h"
13#include "src/gpu/BufferWriter.h"
14#include "src/gpu/GrCaps.h"
15#include "src/gpu/GrDrawOpTest.h"
16#include "src/gpu/GrGeometryProcessor.h"
17#include "src/gpu/GrOpFlushState.h"
18#include "src/gpu/GrProcessor.h"
19#include "src/gpu/GrProgramInfo.h"
20#include "src/gpu/GrResourceProvider.h"
21#include "src/gpu/GrShaderCaps.h"
22#include "src/gpu/GrStyle.h"
23#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
24#include "src/gpu/glsl/GrGLSLProgramDataManager.h"
25#include "src/gpu/glsl/GrGLSLUniformHandler.h"
26#include "src/gpu/glsl/GrGLSLVarying.h"
27#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
28#include "src/gpu/ops/GrMeshDrawOp.h"
29#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
30
31#include <utility>
32
33using skgpu::VertexWriter;
34
35namespace {
36
37static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); }
38
39// Produces TriStrip vertex data for an origin-centered rectangle from [-x, -y] to [x, y]
40static inline VertexWriter::TriStrip<float> origin_centered_tri_strip(float x, float y) {
41    return VertexWriter::TriStrip<float>{ -x, -y, x, y };
42};
43
44}  // namespace
45
46///////////////////////////////////////////////////////////////////////////////
47
48/**
49 * The output of this effect is a modulation of the input color and coverage for a circle. It
50 * operates in a space normalized by the circle radius (outer radius in the case of a stroke)
51 * with origin at the circle center. Three vertex attributes are used:
52 *    vec2f : position in device space of the bounding geometry vertices
53 *    vec4ub: color
54 *    vec4f : (p.xy, outerRad, innerRad)
55 *             p is the position in the normalized space.
56 *             outerRad is the outerRadius in device space.
57 *             innerRad is the innerRadius in normalized space (ignored if not stroking).
58 * Additional clip planes are supported for rendering circular arcs. The additional planes are
59 * either intersected or unioned together. Up to three planes are supported (an initial plane,
60 * a plane intersected with the initial plane, and a plane unioned with the first two). Only two
61 * are useful for any given arc, but having all three in one instance allows combining different
62 * types of arcs.
63 * Round caps for stroking are allowed as well. The caps are specified as two circle center points
64 * in the same space as p.xy.
65 */
66
67class CircleGeometryProcessor : public GrGeometryProcessor {
68public:
69    static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool stroke, bool clipPlane,
70                                     bool isectPlane, bool unionPlane, bool roundCaps,
71                                     bool wideColor, const SkMatrix& localMatrix) {
72        return arena->make([&](void* ptr) {
73            return new (ptr) CircleGeometryProcessor(stroke, clipPlane, isectPlane, unionPlane,
74                                                     roundCaps, wideColor, localMatrix);
75        });
76    }
77
78    const char* name() const override { return "CircleGeometryProcessor"; }
79
80    SkString getShaderDfxInfo() const override {
81        SkString format;
82        format.printf("ShaderDfx_CircleGeometry_%d_%d_%d_%d_%d_%d_%d_%d", fStroke, fInClipPlane.isInitialized(),
83            fInIsectPlane.isInitialized(), fInUnionPlane.isInitialized(), fInRoundCapCenters.isInitialized(),
84            fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective());
85        return format;
86    }
87
88    void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
89        b->addBool(fStroke,                             "stroked"        );
90        b->addBool(fInClipPlane.isInitialized(),        "clipPlane"      );
91        b->addBool(fInIsectPlane.isInitialized(),       "isectPlane"     );
92        b->addBool(fInUnionPlane.isInitialized(),       "unionPlane"     );
93        b->addBool(fInRoundCapCenters.isInitialized(),  "roundCapCenters");
94        b->addBits(ProgramImpl::kMatrixKeyBits,
95                   ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix),
96                   "localMatrixType");
97    }
98
99    std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override {
100        return std::make_unique<Impl>();
101    }
102
103private:
104    CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane,
105                            bool roundCaps, bool wideColor, const SkMatrix& localMatrix)
106            : INHERITED(kCircleGeometryProcessor_ClassID)
107            , fLocalMatrix(localMatrix)
108            , fStroke(stroke) {
109        fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
110        fInColor = MakeColorAttribute("inColor", wideColor);
111        fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
112
113        if (clipPlane) {
114            fInClipPlane = {"inClipPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
115        }
116        if (isectPlane) {
117            fInIsectPlane = {"inIsectPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
118        }
119        if (unionPlane) {
120            fInUnionPlane = {"inUnionPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
121        }
122        if (roundCaps) {
123            SkASSERT(stroke);
124            SkASSERT(clipPlane);
125            fInRoundCapCenters =
126                    {"inRoundCapCenters", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
127        }
128        this->setVertexAttributes(&fInPosition, 7);
129    }
130
131    class Impl : public ProgramImpl {
132    public:
133        void setData(const GrGLSLProgramDataManager& pdman,
134                     const GrShaderCaps& shaderCaps,
135                     const GrGeometryProcessor& geomProc) override {
136            SetTransform(pdman,
137                         shaderCaps,
138                         fLocalMatrixUniform,
139                         geomProc.cast<CircleGeometryProcessor>().fLocalMatrix,
140                         &fLocalMatrix);
141        }
142
143    private:
144        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
145            const CircleGeometryProcessor& cgp = args.fGeomProc.cast<CircleGeometryProcessor>();
146            GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
147            GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
148            GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
149            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
150
151            // emit attributes
152            varyingHandler->emitAttributes(cgp);
153            fragBuilder->codeAppend("float4 circleEdge;");
154            varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge.asShaderVar(), "circleEdge");
155            if (cgp.fInClipPlane.isInitialized()) {
156                fragBuilder->codeAppend("half3 clipPlane;");
157                varyingHandler->addPassThroughAttribute(cgp.fInClipPlane.asShaderVar(),
158                                                        "clipPlane");
159            }
160            if (cgp.fInIsectPlane.isInitialized()) {
161                fragBuilder->codeAppend("half3 isectPlane;");
162                varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane.asShaderVar(),
163                                                        "isectPlane");
164            }
165            if (cgp.fInUnionPlane.isInitialized()) {
166                SkASSERT(cgp.fInClipPlane.isInitialized());
167                fragBuilder->codeAppend("half3 unionPlane;");
168                varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane.asShaderVar(),
169                                                        "unionPlane");
170            }
171            GrGLSLVarying capRadius(kFloat_GrSLType);
172            if (cgp.fInRoundCapCenters.isInitialized()) {
173                fragBuilder->codeAppend("float4 roundCapCenters;");
174                varyingHandler->addPassThroughAttribute(cgp.fInRoundCapCenters.asShaderVar(),
175                                                        "roundCapCenters");
176                varyingHandler->addVarying("capRadius", &capRadius,
177                                           GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
178                // This is the cap radius in normalized space where the outer radius is 1 and
179                // circledEdge.w is the normalized inner radius.
180                vertBuilder->codeAppendf("%s = (1.0 - %s.w) / 2.0;", capRadius.vsOut(),
181                                         cgp.fInCircleEdge.name());
182            }
183
184            // setup pass through color
185            fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
186            varyingHandler->addPassThroughAttribute(cgp.fInColor.asShaderVar(), args.fOutputColor);
187
188            // Setup position
189            WriteOutputPosition(vertBuilder, gpArgs, cgp.fInPosition.name());
190            WriteLocalCoord(vertBuilder,
191                            uniformHandler,
192                            *args.fShaderCaps,
193                            gpArgs,
194                            cgp.fInPosition.asShaderVar(),
195                            cgp.fLocalMatrix,
196                            &fLocalMatrixUniform);
197
198            fragBuilder->codeAppend("float d = length(circleEdge.xy);");
199            fragBuilder->codeAppend("half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));");
200            fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);");
201            if (cgp.fStroke) {
202                fragBuilder->codeAppend(
203                        "half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w));");
204                fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);");
205                fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
206            }
207
208            if (cgp.fInClipPlane.isInitialized()) {
209                fragBuilder->codeAppend(
210                        "half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, "
211                        "clipPlane.xy) + clipPlane.z));");
212                if (cgp.fInIsectPlane.isInitialized()) {
213                    fragBuilder->codeAppend(
214                            "clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, "
215                            "isectPlane.xy) + isectPlane.z));");
216                }
217                if (cgp.fInUnionPlane.isInitialized()) {
218                    fragBuilder->codeAppend(
219                            "clip = saturate(clip + half(saturate(circleEdge.z * dot(circleEdge.xy,"
220                            " unionPlane.xy) + unionPlane.z)));");
221                }
222                fragBuilder->codeAppend("edgeAlpha *= clip;");
223                if (cgp.fInRoundCapCenters.isInitialized()) {
224                    // We compute coverage of the round caps as circles at the butt caps produced
225                    // by the clip planes. The inverse of the clip planes is applied so that there
226                    // is no double counting.
227                    fragBuilder->codeAppendf(
228                            "half dcap1 = half(circleEdge.z * (%s - length(circleEdge.xy - "
229                            "                                              roundCapCenters.xy)));"
230                            "half dcap2 = half(circleEdge.z * (%s - length(circleEdge.xy - "
231                            "                                              roundCapCenters.zw)));"
232                            "half capAlpha = (1 - clip) * (max(dcap1, 0) + max(dcap2, 0));"
233                            "edgeAlpha = min(edgeAlpha + capAlpha, 1.0);",
234                            capRadius.fsIn(), capRadius.fsIn());
235                }
236            }
237            fragBuilder->codeAppendf("half4 %s = half4(edgeAlpha);", args.fOutputCoverage);
238        }
239
240        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
241        UniformHandle fLocalMatrixUniform;
242    };
243
244    SkMatrix fLocalMatrix;
245
246    Attribute fInPosition;
247    Attribute fInColor;
248    Attribute fInCircleEdge;
249    // Optional attributes.
250    Attribute fInClipPlane;
251    Attribute fInIsectPlane;
252    Attribute fInUnionPlane;
253    Attribute fInRoundCapCenters;
254
255    bool fStroke;
256    GR_DECLARE_GEOMETRY_PROCESSOR_TEST
257
258    using INHERITED = GrGeometryProcessor;
259};
260
261GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor);
262
263#if GR_TEST_UTILS
264GrGeometryProcessor* CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
265    bool stroke = d->fRandom->nextBool();
266    bool roundCaps = stroke ? d->fRandom->nextBool() : false;
267    bool wideColor = d->fRandom->nextBool();
268    bool clipPlane = d->fRandom->nextBool();
269    bool isectPlane = d->fRandom->nextBool();
270    bool unionPlane = d->fRandom->nextBool();
271    const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
272    return CircleGeometryProcessor::Make(d->allocator(), stroke, clipPlane, isectPlane,
273                                         unionPlane, roundCaps, wideColor, matrix);
274}
275#endif
276
277class ButtCapDashedCircleGeometryProcessor : public GrGeometryProcessor {
278public:
279    static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool wideColor,
280                                     const SkMatrix& localMatrix) {
281        return arena->make([&](void* ptr) {
282            return new (ptr) ButtCapDashedCircleGeometryProcessor(wideColor, localMatrix);
283        });
284    }
285
286    ~ButtCapDashedCircleGeometryProcessor() override {}
287
288    const char* name() const override { return "ButtCapDashedCircleGeometryProcessor"; }
289
290    SkString getShaderDfxInfo() const override {
291        SkString format;
292        format.printf("ShaderDfx_ButtCapDashedCircle_%d_%d_%d",
293            fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective());
294        return format;
295    }
296
297    void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
298        b->addBits(ProgramImpl::kMatrixKeyBits,
299                   ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix),
300                   "localMatrixType");
301    }
302
303    std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override {
304        return std::make_unique<Impl>();
305    }
306
307private:
308    ButtCapDashedCircleGeometryProcessor(bool wideColor, const SkMatrix& localMatrix)
309            : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID)
310            , fLocalMatrix(localMatrix) {
311        fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
312        fInColor = MakeColorAttribute("inColor", wideColor);
313        fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
314        fInDashParams = {"inDashParams", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
315        this->setVertexAttributes(&fInPosition, 4);
316    }
317
318    class Impl : public ProgramImpl {
319    public:
320        void setData(const GrGLSLProgramDataManager& pdman,
321                     const GrShaderCaps& shaderCaps,
322                     const GrGeometryProcessor& geomProc) override {
323            SetTransform(pdman,
324                         shaderCaps,
325                         fLocalMatrixUniform,
326                         geomProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix,
327                         &fLocalMatrix);
328        }
329
330    private:
331        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
332            const ButtCapDashedCircleGeometryProcessor& bcscgp =
333                    args.fGeomProc.cast<ButtCapDashedCircleGeometryProcessor>();
334            GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
335            GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
336            GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
337            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
338
339            // emit attributes
340            varyingHandler->emitAttributes(bcscgp);
341            fragBuilder->codeAppend("float4 circleEdge;");
342            varyingHandler->addPassThroughAttribute(bcscgp.fInCircleEdge.asShaderVar(),
343                                                    "circleEdge");
344
345            fragBuilder->codeAppend("float4 dashParams;");
346            varyingHandler->addPassThroughAttribute(
347                    bcscgp.fInDashParams.asShaderVar(),
348                    "dashParams",
349                    GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
350            GrGLSLVarying wrapDashes(kHalf4_GrSLType);
351            varyingHandler->addVarying("wrapDashes", &wrapDashes,
352                                       GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
353            GrGLSLVarying lastIntervalLength(kHalf_GrSLType);
354            varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength,
355                                       GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
356            vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.fInDashParams.name());
357            // Our fragment shader works in on/off intervals as specified by dashParams.xy:
358            //     x = length of on interval, y = length of on + off.
359            // There are two other parameters in dashParams.zw:
360            //     z = start angle in radians, w = phase offset in radians in range -y/2..y/2.
361            // Each interval has a "corresponding" dash which may be shifted partially or
362            // fully out of its interval by the phase. So there may be up to two "visual"
363            // dashes in an interval.
364            // When computing coverage in an interval we look at three dashes. These are the
365            // "corresponding" dashes from the current, previous, and next intervals. Any of these
366            // may be phase shifted into our interval or even when phase=0 they may be within half a
367            // pixel distance of a pixel center in the interval.
368            // When in the first interval we need to check the dash from the last interval. And
369            // similarly when in the last interval we need to check the dash from the first
370            // interval. When 2pi is not perfectly divisible dashParams.y this is a boundary case.
371            // We compute the dash begin/end angles in the vertex shader and apply them in the
372            // fragment shader when we detect we're in the first/last interval.
373            vertBuilder->codeAppend(R"(
374                    // The two boundary dash intervals are stored in wrapDashes.xy and .zw and fed
375                    // to the fragment shader as a varying.
376                    float4 wrapDashes;
377                    half lastIntervalLength = mod(6.28318530718, half(dashParams.y));
378                    // We can happen to be perfectly divisible.
379                    if (0 == lastIntervalLength) {
380                        lastIntervalLength = half(dashParams.y);
381                    }
382                    // Let 'l' be the last interval before reaching 2 pi.
383                    // Based on the phase determine whether (l-1)th, l-th, or (l+1)th interval's
384                    // "corresponding" dash appears in the l-th interval and is closest to the 0-th
385                    // interval.
386                    half offset = 0;
387                    if (-dashParams.w >= lastIntervalLength) {
388                         offset = half(-dashParams.y);
389                    } else if (dashParams.w > dashParams.y - lastIntervalLength) {
390                         offset = half(dashParams.y);
391                    }
392                    wrapDashes.x = -lastIntervalLength + offset - dashParams.w;
393                    // The end of this dash may be beyond the 2 pi and therefore clipped. Hence the
394                    // min.
395                    wrapDashes.y = min(wrapDashes.x + dashParams.x, 0);
396
397                    // Based on the phase determine whether the -1st, 0th, or 1st interval's
398                    // "corresponding" dash appears in the 0th interval and is closest to l.
399                    offset = 0;
400                    if (dashParams.w >= dashParams.x) {
401                        offset = half(dashParams.y);
402                    } else if (-dashParams.w > dashParams.y - dashParams.x) {
403                        offset = half(-dashParams.y);
404                    }
405                    wrapDashes.z = lastIntervalLength + offset - dashParams.w;
406                    wrapDashes.w = wrapDashes.z + dashParams.x;
407                    // The start of the dash we're considering may be clipped by the start of the
408                    // circle.
409                    wrapDashes.z = max(wrapDashes.z, lastIntervalLength);
410            )");
411            vertBuilder->codeAppendf("%s = half4(wrapDashes);", wrapDashes.vsOut());
412            vertBuilder->codeAppendf("%s = lastIntervalLength;", lastIntervalLength.vsOut());
413            fragBuilder->codeAppendf("half4 wrapDashes = %s;", wrapDashes.fsIn());
414            fragBuilder->codeAppendf("half lastIntervalLength = %s;", lastIntervalLength.fsIn());
415
416            // setup pass through color
417            fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
418            varyingHandler->addPassThroughAttribute(
419                    bcscgp.fInColor.asShaderVar(),
420                    args.fOutputColor,
421                    GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
422
423            // Setup position
424            WriteOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition.name());
425            WriteLocalCoord(vertBuilder,
426                            uniformHandler,
427                            *args.fShaderCaps,
428                            gpArgs,
429                            bcscgp.fInPosition.asShaderVar(),
430                            bcscgp.fLocalMatrix,
431                            &fLocalMatrixUniform);
432
433            GrShaderVar fnArgs[] = {
434                    GrShaderVar("angleToEdge", kFloat_GrSLType),
435                    GrShaderVar("diameter", kFloat_GrSLType),
436            };
437            SkString fnName = fragBuilder->getMangledFunctionName("coverage_from_dash_edge");
438            fragBuilder->emitFunction(kFloat_GrSLType, fnName.c_str(),
439                                      {fnArgs, SK_ARRAY_COUNT(fnArgs)}, R"(
440                    float linearDist;
441                    angleToEdge = clamp(angleToEdge, -3.1415, 3.1415);
442                    linearDist = diameter * sin(angleToEdge / 2);
443                    return saturate(linearDist + 0.5);
444            )");
445            fragBuilder->codeAppend(R"(
446                    float d = length(circleEdge.xy) * circleEdge.z;
447
448                    // Compute coverage from outer/inner edges of the stroke.
449                    half distanceToOuterEdge = half(circleEdge.z - d);
450                    half edgeAlpha = saturate(distanceToOuterEdge);
451                    half distanceToInnerEdge = half(d - circleEdge.z * circleEdge.w);
452                    half innerAlpha = saturate(distanceToInnerEdge);
453                    edgeAlpha *= innerAlpha;
454
455                    half angleFromStart = half(atan(circleEdge.y, circleEdge.x) - dashParams.z);
456                    angleFromStart = mod(angleFromStart, 6.28318530718);
457                    float x = mod(angleFromStart, dashParams.y);
458                    // Convert the radial distance from center to pixel into a diameter.
459                    d *= 2;
460                    half2 currDash = half2(half(-dashParams.w), half(dashParams.x) -
461                                                                half(dashParams.w));
462                    half2 nextDash = half2(half(dashParams.y) - half(dashParams.w),
463                                           half(dashParams.y) + half(dashParams.x) -
464                                                                half(dashParams.w));
465                    half2 prevDash = half2(half(-dashParams.y) - half(dashParams.w),
466                                           half(-dashParams.y) + half(dashParams.x) -
467                                                                 half(dashParams.w));
468                    half dashAlpha = 0;
469                )");
470            fragBuilder->codeAppendf(R"(
471                    if (angleFromStart - x + dashParams.y >= 6.28318530718) {
472                         dashAlpha += half(%s(x - wrapDashes.z, d) * %s(wrapDashes.w - x, d));
473                         currDash.y = min(currDash.y, lastIntervalLength);
474                         if (nextDash.x >= lastIntervalLength) {
475                             // The next dash is outside the 0..2pi range, throw it away
476                             nextDash.xy = half2(1000);
477                         } else {
478                             // Clip the end of the next dash to the end of the circle
479                             nextDash.y = min(nextDash.y, lastIntervalLength);
480                         }
481                    }
482            )", fnName.c_str(), fnName.c_str());
483            fragBuilder->codeAppendf(R"(
484                    if (angleFromStart - x - dashParams.y < -0.01) {
485                         dashAlpha += half(%s(x - wrapDashes.x, d) * %s(wrapDashes.y - x, d));
486                         currDash.x = max(currDash.x, 0);
487                         if (prevDash.y <= 0) {
488                             // The previous dash is outside the 0..2pi range, throw it away
489                             prevDash.xy = half2(1000);
490                         } else {
491                             // Clip the start previous dash to the start of the circle
492                             prevDash.x = max(prevDash.x, 0);
493                         }
494                    }
495            )", fnName.c_str(), fnName.c_str());
496            fragBuilder->codeAppendf(R"(
497                    dashAlpha += half(%s(x - currDash.x, d) * %s(currDash.y - x, d));
498                    dashAlpha += half(%s(x - nextDash.x, d) * %s(nextDash.y - x, d));
499                    dashAlpha += half(%s(x - prevDash.x, d) * %s(prevDash.y - x, d));
500                    dashAlpha = min(dashAlpha, 1);
501                    edgeAlpha *= dashAlpha;
502            )", fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(),
503                fnName.c_str());
504            fragBuilder->codeAppendf("half4 %s = half4(edgeAlpha);", args.fOutputCoverage);
505        }
506
507        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
508        UniformHandle fLocalMatrixUniform;
509    };
510
511    SkMatrix fLocalMatrix;
512    Attribute fInPosition;
513    Attribute fInColor;
514    Attribute fInCircleEdge;
515    Attribute fInDashParams;
516
517    GR_DECLARE_GEOMETRY_PROCESSOR_TEST
518
519    using INHERITED = GrGeometryProcessor;
520};
521
522#if GR_TEST_UTILS
523GrGeometryProcessor* ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
524    bool wideColor = d->fRandom->nextBool();
525    const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
526    return ButtCapDashedCircleGeometryProcessor::Make(d->allocator(), wideColor, matrix);
527}
528#endif
529
530///////////////////////////////////////////////////////////////////////////////
531
532/**
533 * The output of this effect is a modulation of the input color and coverage for an axis-aligned
534 * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii,
535 * in both x and y directions.
536 *
537 * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0.
538 */
539
540class EllipseGeometryProcessor : public GrGeometryProcessor {
541public:
542    static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool stroke, bool wideColor,
543                                     bool useScale, const SkMatrix& localMatrix) {
544        return arena->make([&](void* ptr) {
545            return new (ptr) EllipseGeometryProcessor(stroke, wideColor, useScale, localMatrix);
546        });
547    }
548
549    ~EllipseGeometryProcessor() override {}
550
551    const char* name() const override { return "EllipseGeometryProcessor"; }
552
553    SkString getShaderDfxInfo() const override {
554        SkString format;
555        format.printf("ShaderDfx_EllipseGeometry_%d_%d_%d_%d", fStroke,
556            fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective());
557        return format;
558    }
559
560    void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
561        b->addBool(fStroke, "stroked");
562        b->addBits(ProgramImpl::kMatrixKeyBits,
563                   ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix),
564                   "localMatrixType");
565    }
566
567    std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override {
568        return std::make_unique<Impl>();
569    }
570
571private:
572    EllipseGeometryProcessor(bool stroke, bool wideColor, bool useScale,
573                             const SkMatrix& localMatrix)
574            : INHERITED(kEllipseGeometryProcessor_ClassID)
575            , fLocalMatrix(localMatrix)
576            , fStroke(stroke)
577            , fUseScale(useScale) {
578        fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
579        fInColor = MakeColorAttribute("inColor", wideColor);
580        if (useScale) {
581            fInEllipseOffset = {"inEllipseOffset", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
582        } else {
583            fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
584        }
585        fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
586        this->setVertexAttributes(&fInPosition, 4);
587    }
588
589    class Impl : public ProgramImpl {
590    public:
591        void setData(const GrGLSLProgramDataManager& pdman,
592                     const GrShaderCaps& shaderCaps,
593                     const GrGeometryProcessor& geomProc) override {
594            const EllipseGeometryProcessor& egp = geomProc.cast<EllipseGeometryProcessor>();
595            SetTransform(pdman, shaderCaps, fLocalMatrixUniform, egp.fLocalMatrix, &fLocalMatrix);
596        }
597
598    private:
599        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
600            const EllipseGeometryProcessor& egp = args.fGeomProc.cast<EllipseGeometryProcessor>();
601            GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
602            GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
603            GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
604
605            // emit attributes
606            varyingHandler->emitAttributes(egp);
607
608            GrSLType offsetType = egp.fUseScale ? kFloat3_GrSLType : kFloat2_GrSLType;
609            GrGLSLVarying ellipseOffsets(offsetType);
610            varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets);
611            vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(),
612                                     egp.fInEllipseOffset.name());
613
614            GrGLSLVarying ellipseRadii(kFloat4_GrSLType);
615            varyingHandler->addVarying("EllipseRadii", &ellipseRadii);
616            vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii.name());
617
618            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
619            // setup pass through color
620            fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
621            varyingHandler->addPassThroughAttribute(egp.fInColor.asShaderVar(), args.fOutputColor);
622
623            // Setup position
624            WriteOutputPosition(vertBuilder, gpArgs, egp.fInPosition.name());
625            WriteLocalCoord(vertBuilder,
626                            uniformHandler,
627                            *args.fShaderCaps,
628                            gpArgs,
629                            egp.fInPosition.asShaderVar(),
630                            egp.fLocalMatrix,
631                            &fLocalMatrixUniform);
632
633            // For stroked ellipses, we use the full ellipse equation (x^2/a^2 + y^2/b^2 = 1)
634            // to compute both the edges because we need two separate test equations for
635            // the single offset.
636            // For filled ellipses we can use a unit circle equation (x^2 + y^2 = 1), and warp
637            // the distance by the gradient, non-uniformly scaled by the inverse of the
638            // ellipse size.
639
640            // On medium precision devices, we scale the denominator of the distance equation
641            // before taking the inverse square root to minimize the chance that we're dividing
642            // by zero, then we scale the result back.
643
644            // for outer curve
645            fragBuilder->codeAppendf("float2 offset = %s.xy;", ellipseOffsets.fsIn());
646            if (egp.fStroke) {
647                fragBuilder->codeAppendf("offset *= %s.xy;", ellipseRadii.fsIn());
648            }
649            fragBuilder->codeAppend("float test = dot(offset, offset) - 1.0;");
650            if (egp.fUseScale) {
651                fragBuilder->codeAppendf("float2 grad = 2.0*offset*(%s.z*%s.xy);",
652                                         ellipseOffsets.fsIn(), ellipseRadii.fsIn());
653            } else {
654                fragBuilder->codeAppendf("float2 grad = 2.0*offset*%s.xy;", ellipseRadii.fsIn());
655            }
656            fragBuilder->codeAppend("float grad_dot = dot(grad, grad);");
657
658            // avoid calling inversesqrt on zero.
659            if (args.fShaderCaps->floatIs32Bits()) {
660                fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);");
661            } else {
662                fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
663            }
664            if (egp.fUseScale) {
665                fragBuilder->codeAppendf("float invlen = %s.z*inversesqrt(grad_dot);",
666                                         ellipseOffsets.fsIn());
667            } else {
668                fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
669            }
670            fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
671
672            // for inner curve
673            if (egp.fStroke) {
674                fragBuilder->codeAppendf("offset = %s.xy*%s.zw;", ellipseOffsets.fsIn(),
675                                         ellipseRadii.fsIn());
676                fragBuilder->codeAppend("test = dot(offset, offset) - 1.0;");
677                if (egp.fUseScale) {
678                    fragBuilder->codeAppendf("grad = 2.0*offset*(%s.z*%s.zw);",
679                                             ellipseOffsets.fsIn(), ellipseRadii.fsIn());
680                } else {
681                    fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn());
682                }
683                fragBuilder->codeAppend("grad_dot = dot(grad, grad);");
684                if (!args.fShaderCaps->floatIs32Bits()) {
685                    fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
686                }
687                if (egp.fUseScale) {
688                    fragBuilder->codeAppendf("invlen = %s.z*inversesqrt(grad_dot);",
689                                             ellipseOffsets.fsIn());
690                } else {
691                    fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
692                }
693                fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
694            }
695
696            fragBuilder->codeAppendf("half4 %s = half4(half(edgeAlpha));", args.fOutputCoverage);
697        }
698
699        using INHERITED = ProgramImpl;
700
701        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
702        UniformHandle fLocalMatrixUniform;
703    };
704
705    Attribute fInPosition;
706    Attribute fInColor;
707    Attribute fInEllipseOffset;
708    Attribute fInEllipseRadii;
709
710    SkMatrix fLocalMatrix;
711    bool fStroke;
712    bool fUseScale;
713
714    GR_DECLARE_GEOMETRY_PROCESSOR_TEST
715
716    using INHERITED = GrGeometryProcessor;
717};
718
719GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor);
720
721#if GR_TEST_UTILS
722GrGeometryProcessor* EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
723    bool stroke = d->fRandom->nextBool();
724    bool wideColor = d->fRandom->nextBool();
725    bool useScale = d->fRandom->nextBool();
726    SkMatrix matrix = GrTest::TestMatrix(d->fRandom);
727    return EllipseGeometryProcessor::Make(d->allocator(), stroke, wideColor, useScale, matrix);
728}
729#endif
730
731///////////////////////////////////////////////////////////////////////////////
732
733/**
734 * The output of this effect is a modulation of the input color and coverage for an ellipse,
735 * specified as a 2D offset from center for both the outer and inner paths (if stroked). The
736 * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by
737 * using differentials.
738 *
739 * The result is device-independent and can be used with any affine matrix.
740 */
741
742enum class DIEllipseStyle { kStroke = 0, kHairline, kFill };
743
744class DIEllipseGeometryProcessor : public GrGeometryProcessor {
745public:
746    static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool wideColor, bool useScale,
747                                     const SkMatrix& viewMatrix, DIEllipseStyle style) {
748        return arena->make([&](void* ptr) {
749            return new (ptr) DIEllipseGeometryProcessor(wideColor, useScale, viewMatrix, style);
750        });
751    }
752
753    ~DIEllipseGeometryProcessor() override {}
754
755    const char* name() const override { return "DIEllipseGeometryProcessor"; }
756
757    SkString getShaderDfxInfo() const override {
758        SkString format;
759        format.printf("ShaderDfx_DIEllipseGeometry_%d_%d_%d_%d", fStyle,
760            fViewMatrix.isIdentity(), fViewMatrix.isScaleTranslate(), fViewMatrix.hasPerspective());
761        return format;
762    }
763
764    void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
765        b->addBits(2, static_cast<uint32_t>(fStyle), "style");
766        b->addBits(ProgramImpl::kMatrixKeyBits,
767                   ProgramImpl::ComputeMatrixKey(caps, fViewMatrix),
768                   "viewMatrixType");
769    }
770
771    std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override {
772        return std::make_unique<Impl>();
773    }
774
775private:
776    DIEllipseGeometryProcessor(bool wideColor, bool useScale, const SkMatrix& viewMatrix,
777                               DIEllipseStyle style)
778            : INHERITED(kDIEllipseGeometryProcessor_ClassID)
779            , fViewMatrix(viewMatrix)
780            , fUseScale(useScale)
781            , fStyle(style) {
782        fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
783        fInColor = MakeColorAttribute("inColor", wideColor);
784        if (useScale) {
785            fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat3_GrVertexAttribType,
786                                  kFloat3_GrSLType};
787        } else {
788            fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType,
789                                  kFloat2_GrSLType};
790        }
791        fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
792        this->setVertexAttributes(&fInPosition, 4);
793    }
794
795    class Impl : public ProgramImpl {
796    public:
797        void setData(const GrGLSLProgramDataManager& pdman,
798                     const GrShaderCaps& shaderCaps,
799                     const GrGeometryProcessor& geomProc) override {
800            const auto& diegp = geomProc.cast<DIEllipseGeometryProcessor>();
801
802            SetTransform(pdman, shaderCaps, fViewMatrixUniform, diegp.fViewMatrix, &fViewMatrix);
803        }
804
805    private:
806        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
807            const auto& diegp = args.fGeomProc.cast<DIEllipseGeometryProcessor>();
808            GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
809            GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
810            GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
811
812            // emit attributes
813            varyingHandler->emitAttributes(diegp);
814
815            GrSLType offsetType = (diegp.fUseScale) ? kFloat3_GrSLType : kFloat2_GrSLType;
816            GrGLSLVarying offsets0(offsetType);
817            varyingHandler->addVarying("EllipseOffsets0", &offsets0);
818            vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.fInEllipseOffsets0.name());
819
820            GrGLSLVarying offsets1(kFloat2_GrSLType);
821            varyingHandler->addVarying("EllipseOffsets1", &offsets1);
822            vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1.name());
823
824            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
825            fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
826            varyingHandler->addPassThroughAttribute(diegp.fInColor.asShaderVar(),
827                                                    args.fOutputColor);
828
829            // Setup position
830            WriteOutputPosition(vertBuilder,
831                                uniformHandler,
832                                *args.fShaderCaps,
833                                gpArgs,
834                                diegp.fInPosition.name(),
835                                diegp.fViewMatrix,
836                                &fViewMatrixUniform);
837            gpArgs->fLocalCoordVar = diegp.fInPosition.asShaderVar();
838
839            // for outer curve
840            fragBuilder->codeAppendf("float2 scaledOffset = %s.xy;", offsets0.fsIn());
841            fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;");
842            fragBuilder->codeAppendf("float2 duvdx = dFdx(%s.xy);", offsets0.fsIn());
843            fragBuilder->codeAppendf("float2 duvdy = dFdy(%s.xy);", offsets0.fsIn());
844            fragBuilder->codeAppendf(
845                    "float2 grad = float2(%s.x*duvdx.x + %s.y*duvdx.y,"
846                    "                     %s.x*duvdy.x + %s.y*duvdy.y);",
847                    offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn());
848            if (diegp.fUseScale) {
849                fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn());
850            }
851
852            fragBuilder->codeAppend("float grad_dot = 4.0*dot(grad, grad);");
853            // avoid calling inversesqrt on zero.
854            if (args.fShaderCaps->floatIs32Bits()) {
855                fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);");
856            } else {
857                fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
858            }
859            fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
860            if (diegp.fUseScale) {
861                fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
862            }
863            if (DIEllipseStyle::kHairline == diegp.fStyle) {
864                // can probably do this with one step
865                fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);");
866                fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);");
867            } else {
868                fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
869            }
870
871            // for inner curve
872            if (DIEllipseStyle::kStroke == diegp.fStyle) {
873                fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn());
874                fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;");
875                fragBuilder->codeAppendf("duvdx = float2(dFdx(%s));", offsets1.fsIn());
876                fragBuilder->codeAppendf("duvdy = float2(dFdy(%s));", offsets1.fsIn());
877                fragBuilder->codeAppendf(
878                        "grad = float2(%s.x*duvdx.x + %s.y*duvdx.y,"
879                        "              %s.x*duvdy.x + %s.y*duvdy.y);",
880                        offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn());
881                if (diegp.fUseScale) {
882                    fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn());
883                }
884                fragBuilder->codeAppend("grad_dot = 4.0*dot(grad, grad);");
885                if (!args.fShaderCaps->floatIs32Bits()) {
886                    fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
887                }
888                fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
889                if (diegp.fUseScale) {
890                    fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
891                }
892                fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
893            }
894
895            fragBuilder->codeAppendf("half4 %s = half4(half(edgeAlpha));", args.fOutputCoverage);
896        }
897
898        SkMatrix fViewMatrix = SkMatrix::InvalidMatrix();
899        UniformHandle fViewMatrixUniform;
900    };
901
902    Attribute fInPosition;
903    Attribute fInColor;
904    Attribute fInEllipseOffsets0;
905    Attribute fInEllipseOffsets1;
906
907    SkMatrix fViewMatrix;
908    bool fUseScale;
909    DIEllipseStyle fStyle;
910
911    GR_DECLARE_GEOMETRY_PROCESSOR_TEST
912
913    using INHERITED = GrGeometryProcessor;
914};
915
916GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor);
917
918#if GR_TEST_UTILS
919GrGeometryProcessor* DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
920    bool wideColor = d->fRandom->nextBool();
921    bool useScale = d->fRandom->nextBool();
922    SkMatrix matrix = GrTest::TestMatrix(d->fRandom);
923    auto style = (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2));
924    return DIEllipseGeometryProcessor::Make(d->allocator(), wideColor, useScale, matrix, style);
925}
926#endif
927
928///////////////////////////////////////////////////////////////////////////////
929
930// We have two possible cases for geometry for a circle:
931
932// In the case of a normal fill, we draw geometry for the circle as an octagon.
933static const uint16_t gFillCircleIndices[] = {
934        // enter the octagon
935        // clang-format off
936        0, 1, 8, 1, 2, 8,
937        2, 3, 8, 3, 4, 8,
938        4, 5, 8, 5, 6, 8,
939        6, 7, 8, 7, 0, 8
940        // clang-format on
941};
942
943// For stroked circles, we use two nested octagons.
944static const uint16_t gStrokeCircleIndices[] = {
945        // enter the octagon
946        // clang-format off
947        0, 1,  9, 0, 9,   8,
948        1, 2, 10, 1, 10,  9,
949        2, 3, 11, 2, 11, 10,
950        3, 4, 12, 3, 12, 11,
951        4, 5, 13, 4, 13, 12,
952        5, 6, 14, 5, 14, 13,
953        6, 7, 15, 6, 15, 14,
954        7, 0,  8, 7,  8, 15,
955        // clang-format on
956};
957
958// Normalized geometry for octagons that circumscribe and lie on a circle:
959
960static constexpr SkScalar kOctOffset = 0.41421356237f;  // sqrt(2) - 1
961static constexpr SkPoint kOctagonOuter[] = {
962    SkPoint::Make(-kOctOffset, -1),
963    SkPoint::Make( kOctOffset, -1),
964    SkPoint::Make( 1, -kOctOffset),
965    SkPoint::Make( 1,  kOctOffset),
966    SkPoint::Make( kOctOffset, 1),
967    SkPoint::Make(-kOctOffset, 1),
968    SkPoint::Make(-1,  kOctOffset),
969    SkPoint::Make(-1, -kOctOffset),
970};
971
972// cosine and sine of pi/8
973static constexpr SkScalar kCosPi8 = 0.923579533f;
974static constexpr SkScalar kSinPi8 = 0.382683432f;
975static constexpr SkPoint kOctagonInner[] = {
976    SkPoint::Make(-kSinPi8, -kCosPi8),
977    SkPoint::Make( kSinPi8, -kCosPi8),
978    SkPoint::Make( kCosPi8, -kSinPi8),
979    SkPoint::Make( kCosPi8,  kSinPi8),
980    SkPoint::Make( kSinPi8,  kCosPi8),
981    SkPoint::Make(-kSinPi8,  kCosPi8),
982    SkPoint::Make(-kCosPi8,  kSinPi8),
983    SkPoint::Make(-kCosPi8, -kSinPi8),
984};
985
986static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
987static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
988static const int kVertsPerStrokeCircle = 16;
989static const int kVertsPerFillCircle = 9;
990
991static int circle_type_to_vert_count(bool stroked) {
992    return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
993}
994
995static int circle_type_to_index_count(bool stroked) {
996    return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
997}
998
999static const uint16_t* circle_type_to_indices(bool stroked) {
1000    return stroked ? gStrokeCircleIndices : gFillCircleIndices;
1001}
1002
1003///////////////////////////////////////////////////////////////////////////////
1004
1005class CircleOp final : public GrMeshDrawOp {
1006private:
1007    using Helper = GrSimpleMeshDrawOpHelper;
1008
1009public:
1010    DEFINE_OP_CLASS_ID
1011
1012    /** Optional extra params to render a partial arc rather than a full circle. */
1013    struct ArcParams {
1014        SkScalar fStartAngleRadians;
1015        SkScalar fSweepAngleRadians;
1016        bool fUseCenter;
1017    };
1018
1019    static GrOp::Owner Make(GrRecordingContext* context,
1020                            GrPaint&& paint,
1021                            const SkMatrix& viewMatrix,
1022                            SkPoint center,
1023                            SkScalar radius,
1024                            const GrStyle& style,
1025                            const ArcParams* arcParams = nullptr) {
1026        SkASSERT(circle_stays_circle(viewMatrix));
1027        if (style.hasPathEffect()) {
1028            return nullptr;
1029        }
1030        const SkStrokeRec& stroke = style.strokeRec();
1031        SkStrokeRec::Style recStyle = stroke.getStyle();
1032        if (arcParams) {
1033            // Arc support depends on the style.
1034            switch (recStyle) {
1035                case SkStrokeRec::kStrokeAndFill_Style:
1036                    // This produces a strange result that this op doesn't implement.
1037                    return nullptr;
1038                case SkStrokeRec::kFill_Style:
1039                    // This supports all fills.
1040                    break;
1041                case SkStrokeRec::kStroke_Style:
1042                    // Strokes that don't use the center point are supported with butt and round
1043                    // caps.
1044                    if (arcParams->fUseCenter || stroke.getCap() == SkPaint::kSquare_Cap) {
1045                        return nullptr;
1046                    }
1047                    break;
1048                case SkStrokeRec::kHairline_Style:
1049                    // Hairline only supports butt cap. Round caps could be emulated by slightly
1050                    // extending the angle range if we ever care to.
1051                    if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) {
1052                        return nullptr;
1053                    }
1054                    break;
1055            }
1056        }
1057        return Helper::FactoryHelper<CircleOp>(context, std::move(paint), viewMatrix, center,
1058                                               radius, style, arcParams);
1059    }
1060
1061    CircleOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
1062             const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, const GrStyle& style,
1063             const ArcParams* arcParams)
1064            : GrMeshDrawOp(ClassID())
1065            , fHelper(processorSet, GrAAType::kCoverage) {
1066        const SkStrokeRec& stroke = style.strokeRec();
1067        SkStrokeRec::Style recStyle = stroke.getStyle();
1068
1069        fRoundCaps = false;
1070
1071        viewMatrix.mapPoints(&center, 1);
1072        radius = viewMatrix.mapRadius(radius);
1073        SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
1074
1075        bool isStrokeOnly =
1076                SkStrokeRec::kStroke_Style == recStyle || SkStrokeRec::kHairline_Style == recStyle;
1077        bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
1078
1079        SkScalar innerRadius = -SK_ScalarHalf;
1080        SkScalar outerRadius = radius;
1081        SkScalar halfWidth = 0;
1082        if (hasStroke) {
1083            if (SkScalarNearlyZero(strokeWidth)) {
1084                halfWidth = SK_ScalarHalf;
1085            } else {
1086                halfWidth = SkScalarHalf(strokeWidth);
1087            }
1088
1089            outerRadius += halfWidth;
1090            if (isStrokeOnly) {
1091                innerRadius = radius - halfWidth;
1092            }
1093        }
1094
1095        // The radii are outset for two reasons. First, it allows the shader to simply perform
1096        // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
1097        // Second, the outer radius is used to compute the verts of the bounding box that is
1098        // rendered and the outset ensures the box will cover all partially covered by the circle.
1099        outerRadius += SK_ScalarHalf;
1100        innerRadius -= SK_ScalarHalf;
1101        bool stroked = isStrokeOnly && innerRadius > 0.0f;
1102        fViewMatrixIfUsingLocalCoords = viewMatrix;
1103
1104        // This makes every point fully inside the intersection plane.
1105        static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f};
1106        // This makes every point fully outside the union plane.
1107        static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f};
1108        static constexpr SkPoint kUnusedRoundCaps[] = {{1e10f, 1e10f}, {1e10f, 1e10f}};
1109        SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
1110                                            center.fX + outerRadius, center.fY + outerRadius);
1111        if (arcParams) {
1112            // The shader operates in a space where the circle is translated to be centered at the
1113            // origin. Here we compute points on the unit circle at the starting and ending angles.
1114            SkPoint startPoint, stopPoint;
1115            startPoint.fY = SkScalarSin(arcParams->fStartAngleRadians);
1116            startPoint.fX = SkScalarCos(arcParams->fStartAngleRadians);
1117            SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians;
1118            stopPoint.fY = SkScalarSin(endAngle);
1119            stopPoint.fX = SkScalarCos(endAngle);
1120
1121            // Adjust the start and end points based on the view matrix (to handle rotated arcs)
1122            startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY);
1123            stopPoint = viewMatrix.mapVector(stopPoint.fX, stopPoint.fY);
1124            startPoint.normalize();
1125            stopPoint.normalize();
1126
1127            // We know the matrix is a similarity here. Detect mirroring which will affect how we
1128            // should orient the clip planes for arcs.
1129            SkASSERT(viewMatrix.isSimilarity());
1130            auto upperLeftDet = viewMatrix.getScaleX()*viewMatrix.getScaleY() -
1131                                viewMatrix.getSkewX() *viewMatrix.getSkewY();
1132            if (upperLeftDet < 0) {
1133                std::swap(startPoint, stopPoint);
1134            }
1135
1136            fRoundCaps = style.strokeRec().getWidth() > 0 &&
1137                         style.strokeRec().getCap() == SkPaint::kRound_Cap;
1138            SkPoint roundCaps[2];
1139            if (fRoundCaps) {
1140                // Compute the cap center points in the normalized space.
1141                SkScalar midRadius = (innerRadius + outerRadius) / (2 * outerRadius);
1142                roundCaps[0] = startPoint * midRadius;
1143                roundCaps[1] = stopPoint * midRadius;
1144            } else {
1145                roundCaps[0] = kUnusedRoundCaps[0];
1146                roundCaps[1] = kUnusedRoundCaps[1];
1147            }
1148
1149            // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against
1150            // radial lines. We treat round caps the same way, but tack coverage of circles at the
1151            // center of the butts.
1152            // However, in both cases we have to be careful about the half-circle.
1153            // case. In that case the two radial lines are equal and so that edge gets clipped
1154            // twice. Since the shared edge goes through the center we fall back on the !useCenter
1155            // case.
1156            auto absSweep = SkScalarAbs(arcParams->fSweepAngleRadians);
1157            bool useCenter = (arcParams->fUseCenter || isStrokeOnly) &&
1158                             !SkScalarNearlyEqual(absSweep, SK_ScalarPI);
1159            if (useCenter) {
1160                SkVector norm0 = {startPoint.fY, -startPoint.fX};
1161                SkVector norm1 = {stopPoint.fY, -stopPoint.fX};
1162                // This ensures that norm0 is always the clockwise plane, and norm1 is CCW.
1163                if (arcParams->fSweepAngleRadians < 0) {
1164                    std::swap(norm0, norm1);
1165                }
1166                norm0.negate();
1167                fClipPlane = true;
1168                if (absSweep > SK_ScalarPI) {
1169                    fCircles.emplace_back(Circle{
1170                            color,
1171                            innerRadius,
1172                            outerRadius,
1173                            {norm0.fX, norm0.fY, 0.5f},
1174                            {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1175                            {norm1.fX, norm1.fY, 0.5f},
1176                            {roundCaps[0], roundCaps[1]},
1177                            devBounds,
1178                            stroked});
1179                    fClipPlaneIsect = false;
1180                    fClipPlaneUnion = true;
1181                } else {
1182                    fCircles.emplace_back(Circle{
1183                            color,
1184                            innerRadius,
1185                            outerRadius,
1186                            {norm0.fX, norm0.fY, 0.5f},
1187                            {norm1.fX, norm1.fY, 0.5f},
1188                            {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
1189                            {roundCaps[0], roundCaps[1]},
1190                            devBounds,
1191                            stroked});
1192                    fClipPlaneIsect = true;
1193                    fClipPlaneUnion = false;
1194                }
1195            } else {
1196                // We clip to a secant of the original circle.
1197                startPoint.scale(radius);
1198                stopPoint.scale(radius);
1199                SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX};
1200                norm.normalize();
1201                if (arcParams->fSweepAngleRadians > 0) {
1202                    norm.negate();
1203                }
1204                SkScalar d = -norm.dot(startPoint) + 0.5f;
1205
1206                fCircles.emplace_back(
1207                        Circle{color,
1208                               innerRadius,
1209                               outerRadius,
1210                               {norm.fX, norm.fY, d},
1211                               {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1212                               {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
1213                               {roundCaps[0], roundCaps[1]},
1214                               devBounds,
1215                               stroked});
1216                fClipPlane = true;
1217                fClipPlaneIsect = false;
1218                fClipPlaneUnion = false;
1219            }
1220        } else {
1221            fCircles.emplace_back(
1222                    Circle{color,
1223                           innerRadius,
1224                           outerRadius,
1225                           {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1226                           {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1227                           {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
1228                           {kUnusedRoundCaps[0], kUnusedRoundCaps[1]},
1229                           devBounds,
1230                           stroked});
1231            fClipPlane = false;
1232            fClipPlaneIsect = false;
1233            fClipPlaneUnion = false;
1234        }
1235        // Use the original radius and stroke radius for the bounds so that it does not include the
1236        // AA bloat.
1237        radius += halfWidth;
1238        this->setBounds(
1239                {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
1240                HasAABloat::kYes, IsHairline::kNo);
1241        fVertCount = circle_type_to_vert_count(stroked);
1242        fIndexCount = circle_type_to_index_count(stroked);
1243        fAllFill = !stroked;
1244    }
1245
1246    const char* name() const override { return "CircleOp"; }
1247
1248    void visitProxies(const GrVisitProxyFunc& func) const override {
1249        if (fProgramInfo) {
1250            fProgramInfo->visitFPProxies(func);
1251        } else {
1252            fHelper.visitProxies(func);
1253        }
1254    }
1255
1256    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
1257                                      GrClampType clampType) override {
1258        SkPMColor4f* color = &fCircles.front().fColor;
1259        return fHelper.finalizeProcessors(caps, clip, clampType,
1260                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
1261                                          &fWideColor);
1262    }
1263
1264    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1265
1266private:
1267    GrProgramInfo* programInfo() override { return fProgramInfo; }
1268
1269    void onCreateProgramInfo(const GrCaps* caps,
1270                             SkArenaAlloc* arena,
1271                             const GrSurfaceProxyView& writeView,
1272                             bool usesMSAASurface,
1273                             GrAppliedClip&& appliedClip,
1274                             const GrDstProxyView& dstProxyView,
1275                             GrXferBarrierFlags renderPassXferBarriers,
1276                             GrLoadOp colorLoadOp) override {
1277        SkASSERT(!usesMSAASurface);
1278
1279        SkMatrix localMatrix;
1280        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
1281            return;
1282        }
1283
1284        GrGeometryProcessor* gp = CircleGeometryProcessor::Make(arena, !fAllFill, fClipPlane,
1285                                                                fClipPlaneIsect, fClipPlaneUnion,
1286                                                                fRoundCaps, fWideColor,
1287                                                                localMatrix);
1288
1289        fProgramInfo = fHelper.createProgramInfo(caps,
1290                                                 arena,
1291                                                 writeView,
1292                                                 usesMSAASurface,
1293                                                 std::move(appliedClip),
1294                                                 dstProxyView,
1295                                                 gp,
1296                                                 GrPrimitiveType::kTriangles,
1297                                                 renderPassXferBarriers,
1298                                                 colorLoadOp);
1299    }
1300
1301    void onPrepareDraws(GrMeshDrawTarget* target) override {
1302        if (!fProgramInfo) {
1303            this->createProgramInfo(target);
1304            if (!fProgramInfo) {
1305                return;
1306            }
1307        }
1308
1309        sk_sp<const GrBuffer> vertexBuffer;
1310        int firstVertex;
1311        VertexWriter vertices{target->makeVertexSpace(fProgramInfo->geomProc().vertexStride(),
1312                                                      fVertCount, &vertexBuffer, &firstVertex)};
1313        if (!vertices) {
1314            SkDebugf("Could not allocate vertices\n");
1315            return;
1316        }
1317
1318        sk_sp<const GrBuffer> indexBuffer = nullptr;
1319        int firstIndex = 0;
1320        uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1321        if (!indices) {
1322            SkDebugf("Could not allocate indices\n");
1323            return;
1324        }
1325
1326        int currStartVertex = 0;
1327        for (const auto& circle : fCircles) {
1328            SkScalar innerRadius = circle.fInnerRadius;
1329            SkScalar outerRadius = circle.fOuterRadius;
1330            GrVertexColor color(circle.fColor, fWideColor);
1331            const SkRect& bounds = circle.fDevBounds;
1332
1333            // The inner radius in the vertex data must be specified in normalized space.
1334            innerRadius = innerRadius / outerRadius;
1335            SkPoint radii = { outerRadius, innerRadius };
1336
1337            SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
1338            SkScalar halfWidth = 0.5f * bounds.width();
1339
1340            SkVector geoClipPlane = { 0, 0 };
1341            SkScalar offsetClipDist = SK_Scalar1;
1342            if (!circle.fStroked && fClipPlane && fClipPlaneIsect &&
1343                    (circle.fClipPlane[0] * circle.fIsectPlane[0] +
1344                     circle.fClipPlane[1] * circle.fIsectPlane[1]) < 0.0f) {
1345                // Acute arc. Clip the vertices to the perpendicular half-plane. We've constructed
1346                // fClipPlane to be clockwise, and fISectPlane to be CCW, so we can can rotate them
1347                // each 90 degrees to point "out", then average them. We back off by 1/2 pixel so
1348                // the AA can extend just past the center of the circle.
1349                geoClipPlane.set(circle.fClipPlane[1] - circle.fIsectPlane[1],
1350                                 circle.fIsectPlane[0] - circle.fClipPlane[0]);
1351                SkAssertResult(geoClipPlane.normalize());
1352                offsetClipDist = 0.5f / halfWidth;
1353            }
1354
1355            for (int i = 0; i < 8; ++i) {
1356                // This clips the normalized offset to the half-plane we computed above. Then we
1357                // compute the vertex position from this.
1358                SkScalar dist = std::min(kOctagonOuter[i].dot(geoClipPlane) + offsetClipDist, 0.0f);
1359                SkVector offset = kOctagonOuter[i] - geoClipPlane * dist;
1360                vertices << (center + offset * halfWidth)
1361                         << color
1362                         << offset
1363                         << radii;
1364                if (fClipPlane) {
1365                    vertices << circle.fClipPlane;
1366                }
1367                if (fClipPlaneIsect) {
1368                    vertices << circle.fIsectPlane;
1369                }
1370                if (fClipPlaneUnion) {
1371                    vertices << circle.fUnionPlane;
1372                }
1373                if (fRoundCaps) {
1374                    vertices << circle.fRoundCapCenters;
1375                }
1376            }
1377
1378            if (circle.fStroked) {
1379                // compute the inner ring
1380
1381                for (int i = 0; i < 8; ++i) {
1382                    vertices << (center + kOctagonInner[i] * circle.fInnerRadius)
1383                             << color
1384                             << kOctagonInner[i] * innerRadius
1385                             << radii;
1386                    if (fClipPlane) {
1387                        vertices << circle.fClipPlane;
1388                    }
1389                    if (fClipPlaneIsect) {
1390                        vertices << circle.fIsectPlane;
1391                    }
1392                    if (fClipPlaneUnion) {
1393                        vertices << circle.fUnionPlane;
1394                    }
1395                    if (fRoundCaps) {
1396                        vertices << circle.fRoundCapCenters;
1397                    }
1398                }
1399            } else {
1400                // filled
1401                vertices << center << color << SkPoint::Make(0, 0) << radii;
1402                if (fClipPlane) {
1403                    vertices << circle.fClipPlane;
1404                }
1405                if (fClipPlaneIsect) {
1406                    vertices << circle.fIsectPlane;
1407                }
1408                if (fClipPlaneUnion) {
1409                    vertices << circle.fUnionPlane;
1410                }
1411                if (fRoundCaps) {
1412                    vertices << circle.fRoundCapCenters;
1413                }
1414            }
1415
1416            const uint16_t* primIndices = circle_type_to_indices(circle.fStroked);
1417            const int primIndexCount = circle_type_to_index_count(circle.fStroked);
1418            for (int i = 0; i < primIndexCount; ++i) {
1419                *indices++ = primIndices[i] + currStartVertex;
1420            }
1421
1422            currStartVertex += circle_type_to_vert_count(circle.fStroked);
1423        }
1424
1425        fMesh = target->allocMesh();
1426        fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
1427                         GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex);
1428    }
1429
1430    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1431        if (!fProgramInfo || !fMesh) {
1432            return;
1433        }
1434
1435        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
1436        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
1437        flushState->drawMesh(*fMesh);
1438    }
1439
1440    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
1441        CircleOp* that = t->cast<CircleOp>();
1442
1443        // can only represent 65535 unique vertices with 16-bit indices
1444        if (fVertCount + that->fVertCount > 65536) {
1445            return CombineResult::kCannotCombine;
1446        }
1447
1448        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
1449            return CombineResult::kCannotCombine;
1450        }
1451
1452        if (fHelper.usesLocalCoords() &&
1453            !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords,
1454                                      that->fViewMatrixIfUsingLocalCoords)) {
1455            return CombineResult::kCannotCombine;
1456        }
1457
1458        // Because we've set up the ops that don't use the planes with noop values
1459        // we can just accumulate used planes by later ops.
1460        fClipPlane |= that->fClipPlane;
1461        fClipPlaneIsect |= that->fClipPlaneIsect;
1462        fClipPlaneUnion |= that->fClipPlaneUnion;
1463        fRoundCaps |= that->fRoundCaps;
1464        fWideColor |= that->fWideColor;
1465
1466        fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
1467        fVertCount += that->fVertCount;
1468        fIndexCount += that->fIndexCount;
1469        fAllFill = fAllFill && that->fAllFill;
1470        return CombineResult::kMerged;
1471    }
1472
1473#if GR_TEST_UTILS
1474    SkString onDumpInfo() const override {
1475        SkString string;
1476        for (int i = 0; i < fCircles.count(); ++i) {
1477            string.appendf(
1478                    "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1479                    "InnerRad: %.2f, OuterRad: %.2f\n",
1480                    fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft,
1481                    fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight,
1482                    fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius,
1483                    fCircles[i].fOuterRadius);
1484        }
1485        string += fHelper.dumpInfo();
1486        return string;
1487    }
1488#endif
1489
1490    struct Circle {
1491        SkPMColor4f fColor;
1492        SkScalar fInnerRadius;
1493        SkScalar fOuterRadius;
1494        SkScalar fClipPlane[3];
1495        SkScalar fIsectPlane[3];
1496        SkScalar fUnionPlane[3];
1497        SkPoint fRoundCapCenters[2];
1498        SkRect fDevBounds;
1499        bool fStroked;
1500    };
1501
1502    SkMatrix fViewMatrixIfUsingLocalCoords;
1503    Helper fHelper;
1504    SkSTArray<1, Circle, true> fCircles;
1505    int fVertCount;
1506    int fIndexCount;
1507    bool fAllFill;
1508    bool fClipPlane;
1509    bool fClipPlaneIsect;
1510    bool fClipPlaneUnion;
1511    bool fRoundCaps;
1512    bool fWideColor;
1513
1514    GrSimpleMesh*  fMesh = nullptr;
1515    GrProgramInfo* fProgramInfo = nullptr;
1516
1517    using INHERITED = GrMeshDrawOp;
1518};
1519
1520class ButtCapDashedCircleOp final : public GrMeshDrawOp {
1521private:
1522    using Helper = GrSimpleMeshDrawOpHelper;
1523
1524public:
1525    DEFINE_OP_CLASS_ID
1526
1527    static GrOp::Owner Make(GrRecordingContext* context,
1528                            GrPaint&& paint,
1529                            const SkMatrix& viewMatrix,
1530                            SkPoint center,
1531                            SkScalar radius,
1532                            SkScalar strokeWidth,
1533                            SkScalar startAngle,
1534                            SkScalar onAngle,
1535                            SkScalar offAngle,
1536                            SkScalar phaseAngle) {
1537        SkASSERT(circle_stays_circle(viewMatrix));
1538        SkASSERT(strokeWidth < 2 * radius);
1539        return Helper::FactoryHelper<ButtCapDashedCircleOp>(context, std::move(paint), viewMatrix,
1540                                                            center, radius, strokeWidth, startAngle,
1541                                                            onAngle, offAngle, phaseAngle);
1542    }
1543
1544    ButtCapDashedCircleOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
1545                          const SkMatrix& viewMatrix, SkPoint center, SkScalar radius,
1546                          SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle,
1547                          SkScalar offAngle, SkScalar phaseAngle)
1548            : GrMeshDrawOp(ClassID())
1549            , fHelper(processorSet, GrAAType::kCoverage) {
1550        SkASSERT(circle_stays_circle(viewMatrix));
1551        viewMatrix.mapPoints(&center, 1);
1552        radius = viewMatrix.mapRadius(radius);
1553        strokeWidth = viewMatrix.mapRadius(strokeWidth);
1554
1555        // Determine the angle where the circle starts in device space and whether its orientation
1556        // has been reversed.
1557        SkVector start;
1558        bool reflection;
1559        if (!startAngle) {
1560            start = {1, 0};
1561        } else {
1562            start.fY = SkScalarSin(startAngle);
1563            start.fX = SkScalarCos(startAngle);
1564        }
1565        viewMatrix.mapVectors(&start, 1);
1566        startAngle = SkScalarATan2(start.fY, start.fX);
1567        reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() -
1568                      viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0;
1569
1570        auto totalAngle = onAngle + offAngle;
1571        phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2;
1572
1573        SkScalar halfWidth = 0;
1574        if (SkScalarNearlyZero(strokeWidth)) {
1575            halfWidth = SK_ScalarHalf;
1576        } else {
1577            halfWidth = SkScalarHalf(strokeWidth);
1578        }
1579
1580        SkScalar outerRadius = radius + halfWidth;
1581        SkScalar innerRadius = radius - halfWidth;
1582
1583        // The radii are outset for two reasons. First, it allows the shader to simply perform
1584        // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
1585        // Second, the outer radius is used to compute the verts of the bounding box that is
1586        // rendered and the outset ensures the box will cover all partially covered by the circle.
1587        outerRadius += SK_ScalarHalf;
1588        innerRadius -= SK_ScalarHalf;
1589        fViewMatrixIfUsingLocalCoords = viewMatrix;
1590
1591        SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
1592                                            center.fX + outerRadius, center.fY + outerRadius);
1593
1594        // We store whether there is a reflection as a negative total angle.
1595        if (reflection) {
1596            totalAngle = -totalAngle;
1597        }
1598        fCircles.push_back(Circle{
1599            color,
1600            outerRadius,
1601            innerRadius,
1602            onAngle,
1603            totalAngle,
1604            startAngle,
1605            phaseAngle,
1606            devBounds
1607        });
1608        // Use the original radius and stroke radius for the bounds so that it does not include the
1609        // AA bloat.
1610        radius += halfWidth;
1611        this->setBounds(
1612                {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
1613                HasAABloat::kYes, IsHairline::kNo);
1614        fVertCount = circle_type_to_vert_count(true);
1615        fIndexCount = circle_type_to_index_count(true);
1616    }
1617
1618    const char* name() const override { return "ButtCappedDashedCircleOp"; }
1619
1620    void visitProxies(const GrVisitProxyFunc& func) const override {
1621        if (fProgramInfo) {
1622            fProgramInfo->visitFPProxies(func);
1623        } else {
1624            fHelper.visitProxies(func);
1625        }
1626    }
1627
1628    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
1629                                      GrClampType clampType) override {
1630        SkPMColor4f* color = &fCircles.front().fColor;
1631        return fHelper.finalizeProcessors(caps, clip, clampType,
1632                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
1633                                          &fWideColor);
1634    }
1635
1636    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1637
1638private:
1639    GrProgramInfo* programInfo() override { return fProgramInfo; }
1640
1641    void onCreateProgramInfo(const GrCaps* caps,
1642                             SkArenaAlloc* arena,
1643                             const GrSurfaceProxyView& writeView,
1644                             bool usesMSAASurface,
1645                             GrAppliedClip&& appliedClip,
1646                             const GrDstProxyView& dstProxyView,
1647                             GrXferBarrierFlags renderPassXferBarriers,
1648                             GrLoadOp colorLoadOp) override {
1649        SkASSERT(!usesMSAASurface);
1650
1651        SkMatrix localMatrix;
1652        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
1653            return;
1654        }
1655
1656        // Setup geometry processor
1657        GrGeometryProcessor* gp = ButtCapDashedCircleGeometryProcessor::Make(arena,
1658                                                                             fWideColor,
1659                                                                             localMatrix);
1660
1661        fProgramInfo = fHelper.createProgramInfo(caps,
1662                                                 arena,
1663                                                 writeView,
1664                                                 usesMSAASurface,
1665                                                 std::move(appliedClip),
1666                                                 dstProxyView,
1667                                                 gp,
1668                                                 GrPrimitiveType::kTriangles,
1669                                                 renderPassXferBarriers,
1670                                                 colorLoadOp);
1671    }
1672
1673    void onPrepareDraws(GrMeshDrawTarget* target) override {
1674        if (!fProgramInfo) {
1675            this->createProgramInfo(target);
1676            if (!fProgramInfo) {
1677                return;
1678            }
1679        }
1680
1681        sk_sp<const GrBuffer> vertexBuffer;
1682        int firstVertex;
1683        VertexWriter vertices{target->makeVertexSpace(fProgramInfo->geomProc().vertexStride(),
1684                                                      fVertCount, &vertexBuffer, &firstVertex)};
1685        if (!vertices) {
1686            SkDebugf("Could not allocate vertices\n");
1687            return;
1688        }
1689
1690        sk_sp<const GrBuffer> indexBuffer;
1691        int firstIndex = 0;
1692        uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1693        if (!indices) {
1694            SkDebugf("Could not allocate indices\n");
1695            return;
1696        }
1697
1698        int currStartVertex = 0;
1699        for (const auto& circle : fCircles) {
1700            // The inner radius in the vertex data must be specified in normalized space so that
1701            // length() can be called with smaller values to avoid precision issues with half
1702            // floats.
1703            auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius;
1704            const SkRect& bounds = circle.fDevBounds;
1705            bool reflect = false;
1706            struct { float onAngle, totalAngle, startAngle, phaseAngle; } dashParams = {
1707                circle.fOnAngle, circle.fTotalAngle, circle.fStartAngle, circle.fPhaseAngle
1708            };
1709            if (dashParams.totalAngle < 0) {
1710                reflect = true;
1711                dashParams.totalAngle = -dashParams.totalAngle;
1712                dashParams.startAngle = -dashParams.startAngle;
1713            }
1714
1715            GrVertexColor color(circle.fColor, fWideColor);
1716
1717            // The bounding geometry for the circle is composed of an outer bounding octagon and
1718            // an inner bounded octagon.
1719
1720            // Compute the vertices of the outer octagon.
1721            SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
1722            SkScalar halfWidth = 0.5f * bounds.width();
1723
1724            auto reflectY = [=](const SkPoint& p) {
1725                return SkPoint{ p.fX, reflect ? -p.fY : p.fY };
1726            };
1727
1728            for (int i = 0; i < 8; ++i) {
1729                vertices << (center + kOctagonOuter[i] * halfWidth)
1730                         << color
1731                         << reflectY(kOctagonOuter[i])
1732                         << circle.fOuterRadius
1733                         << normInnerRadius
1734                         << dashParams;
1735            }
1736
1737            // Compute the vertices of the inner octagon.
1738            for (int i = 0; i < 8; ++i) {
1739                vertices << (center + kOctagonInner[i] * circle.fInnerRadius)
1740                         << color
1741                         << (reflectY(kOctagonInner[i]) * normInnerRadius)
1742                         << circle.fOuterRadius
1743                         << normInnerRadius
1744                         << dashParams;
1745            }
1746
1747            const uint16_t* primIndices = circle_type_to_indices(true);
1748            const int primIndexCount = circle_type_to_index_count(true);
1749            for (int i = 0; i < primIndexCount; ++i) {
1750                *indices++ = primIndices[i] + currStartVertex;
1751            }
1752
1753            currStartVertex += circle_type_to_vert_count(true);
1754        }
1755
1756        fMesh = target->allocMesh();
1757        fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
1758                          GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex);
1759    }
1760
1761    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1762        if (!fProgramInfo || !fMesh) {
1763            return;
1764        }
1765
1766        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
1767        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
1768        flushState->drawMesh(*fMesh);
1769    }
1770
1771    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
1772        ButtCapDashedCircleOp* that = t->cast<ButtCapDashedCircleOp>();
1773
1774        // can only represent 65535 unique vertices with 16-bit indices
1775        if (fVertCount + that->fVertCount > 65536) {
1776            return CombineResult::kCannotCombine;
1777        }
1778
1779        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
1780            return CombineResult::kCannotCombine;
1781        }
1782
1783        if (fHelper.usesLocalCoords() &&
1784            !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords,
1785                                      that->fViewMatrixIfUsingLocalCoords)) {
1786            return CombineResult::kCannotCombine;
1787        }
1788
1789        fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
1790        fVertCount += that->fVertCount;
1791        fIndexCount += that->fIndexCount;
1792        fWideColor |= that->fWideColor;
1793        return CombineResult::kMerged;
1794    }
1795
1796#if GR_TEST_UTILS
1797    SkString onDumpInfo() const override {
1798        SkString string;
1799        for (int i = 0; i < fCircles.count(); ++i) {
1800            string.appendf(
1801                    "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1802                    "InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, "
1803                    "Phase: %.2f\n",
1804                    fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft,
1805                    fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight,
1806                    fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius,
1807                    fCircles[i].fOuterRadius, fCircles[i].fOnAngle, fCircles[i].fTotalAngle,
1808                    fCircles[i].fPhaseAngle);
1809        }
1810        string += fHelper.dumpInfo();
1811        return string;
1812    }
1813#endif
1814
1815    struct Circle {
1816        SkPMColor4f fColor;
1817        SkScalar fOuterRadius;
1818        SkScalar fInnerRadius;
1819        SkScalar fOnAngle;
1820        SkScalar fTotalAngle;
1821        SkScalar fStartAngle;
1822        SkScalar fPhaseAngle;
1823        SkRect fDevBounds;
1824    };
1825
1826    SkMatrix fViewMatrixIfUsingLocalCoords;
1827    Helper fHelper;
1828    SkSTArray<1, Circle, true> fCircles;
1829    int fVertCount;
1830    int fIndexCount;
1831    bool fWideColor;
1832
1833    GrSimpleMesh*  fMesh = nullptr;
1834    GrProgramInfo* fProgramInfo = nullptr;
1835
1836    using INHERITED = GrMeshDrawOp;
1837};
1838
1839///////////////////////////////////////////////////////////////////////////////
1840
1841class EllipseOp : public GrMeshDrawOp {
1842private:
1843    using Helper = GrSimpleMeshDrawOpHelper;
1844
1845    struct DeviceSpaceParams {
1846        SkPoint fCenter;
1847        SkScalar fXRadius;
1848        SkScalar fYRadius;
1849        SkScalar fInnerXRadius;
1850        SkScalar fInnerYRadius;
1851    };
1852
1853public:
1854    DEFINE_OP_CLASS_ID
1855
1856    static GrOp::Owner Make(GrRecordingContext* context,
1857                            GrPaint&& paint,
1858                            const SkMatrix& viewMatrix,
1859                            const SkRect& ellipse,
1860                            const SkStrokeRec& stroke) {
1861        DeviceSpaceParams params;
1862        // do any matrix crunching before we reset the draw state for device coords
1863        params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
1864        viewMatrix.mapPoints(&params.fCenter, 1);
1865        SkScalar ellipseXRadius = SkScalarHalf(ellipse.width());
1866        SkScalar ellipseYRadius = SkScalarHalf(ellipse.height());
1867        params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius +
1868                                      viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius);
1869        params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius +
1870                                      viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius);
1871
1872        // do (potentially) anisotropic mapping of stroke
1873        SkVector scaledStroke;
1874        SkScalar strokeWidth = stroke.getWidth();
1875        scaledStroke.fX = SkScalarAbs(
1876                strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
1877        scaledStroke.fY = SkScalarAbs(
1878                strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
1879
1880        SkStrokeRec::Style style = stroke.getStyle();
1881        bool isStrokeOnly =
1882                SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
1883        bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
1884
1885        params.fInnerXRadius = 0;
1886        params.fInnerYRadius = 0;
1887        if (hasStroke) {
1888            if (SkScalarNearlyZero(scaledStroke.length())) {
1889                scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
1890            } else {
1891                scaledStroke.scale(SK_ScalarHalf);
1892            }
1893
1894            // we only handle thick strokes for near-circular ellipses
1895            if (scaledStroke.length() > SK_ScalarHalf &&
1896                (0.5f * params.fXRadius > params.fYRadius ||
1897                 0.5f * params.fYRadius > params.fXRadius)) {
1898                return nullptr;
1899            }
1900
1901            // we don't handle it if curvature of the stroke is less than curvature of the ellipse
1902            if (scaledStroke.fX * (params.fXRadius * params.fYRadius) <
1903                        (scaledStroke.fY * scaledStroke.fY) * params.fXRadius ||
1904                scaledStroke.fY * (params.fXRadius * params.fXRadius) <
1905                        (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) {
1906                return nullptr;
1907            }
1908
1909            // this is legit only if scale & translation (which should be the case at the moment)
1910            if (isStrokeOnly) {
1911                params.fInnerXRadius = params.fXRadius - scaledStroke.fX;
1912                params.fInnerYRadius = params.fYRadius - scaledStroke.fY;
1913            }
1914
1915            params.fXRadius += scaledStroke.fX;
1916            params.fYRadius += scaledStroke.fY;
1917        }
1918
1919        // For large ovals with low precision floats, we fall back to the path renderer.
1920        // To compute the AA at the edge we divide by the gradient, which is clamped to a
1921        // minimum value to avoid divides by zero. With large ovals and low precision this
1922        // leads to blurring at the edge of the oval.
1923        const SkScalar kMaxOvalRadius = 16384;
1924        if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
1925            (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
1926            return nullptr;
1927        }
1928
1929        return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix,
1930                                                params, stroke);
1931    }
1932
1933    EllipseOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
1934              const SkMatrix& viewMatrix, const DeviceSpaceParams& params,
1935              const SkStrokeRec& stroke)
1936            : INHERITED(ClassID())
1937            , fHelper(processorSet, GrAAType::kCoverage)
1938            , fUseScale(false) {
1939        SkStrokeRec::Style style = stroke.getStyle();
1940        bool isStrokeOnly =
1941                SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
1942
1943        fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius,
1944                                       params.fInnerXRadius, params.fInnerYRadius,
1945                                       SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius,
1946                                                        params.fCenter.fY - params.fYRadius,
1947                                                        params.fCenter.fX + params.fXRadius,
1948                                                        params.fCenter.fY + params.fYRadius)});
1949
1950        this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsHairline::kNo);
1951
1952        fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0;
1953        fViewMatrixIfUsingLocalCoords = viewMatrix;
1954    }
1955
1956    const char* name() const override { return "EllipseOp"; }
1957
1958    void visitProxies(const GrVisitProxyFunc& func) const override {
1959        if (fProgramInfo) {
1960            fProgramInfo->visitFPProxies(func);
1961        } else {
1962            fHelper.visitProxies(func);
1963        }
1964    }
1965
1966    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
1967                                      GrClampType clampType) override {
1968        fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
1969                    !caps.shaderCaps()->hasLowFragmentPrecision();
1970        SkPMColor4f* color = &fEllipses.front().fColor;
1971        return fHelper.finalizeProcessors(caps, clip, clampType,
1972                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
1973                                          &fWideColor);
1974    }
1975
1976    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1977
1978private:
1979    GrProgramInfo* programInfo() override { return fProgramInfo; }
1980
1981    void onCreateProgramInfo(const GrCaps* caps,
1982                             SkArenaAlloc* arena,
1983                             const GrSurfaceProxyView& writeView,
1984                             bool usesMSAASurface,
1985                             GrAppliedClip&& appliedClip,
1986                             const GrDstProxyView& dstProxyView,
1987                             GrXferBarrierFlags renderPassXferBarriers,
1988                             GrLoadOp colorLoadOp) override {
1989        SkMatrix localMatrix;
1990        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
1991            return;
1992        }
1993
1994        GrGeometryProcessor* gp = EllipseGeometryProcessor::Make(arena, fStroked, fWideColor,
1995                                                                 fUseScale, localMatrix);
1996
1997        fProgramInfo = fHelper.createProgramInfo(caps,
1998                                                 arena,
1999                                                 writeView,
2000                                                 usesMSAASurface,
2001                                                 std::move(appliedClip),
2002                                                 dstProxyView,
2003                                                 gp,
2004                                                 GrPrimitiveType::kTriangles,
2005                                                 renderPassXferBarriers,
2006                                                 colorLoadOp);
2007    }
2008
2009    void onPrepareDraws(GrMeshDrawTarget* target) override {
2010        if (!fProgramInfo) {
2011            this->createProgramInfo(target);
2012            if (!fProgramInfo) {
2013                return;
2014            }
2015        }
2016
2017        QuadHelper helper(target, fProgramInfo->geomProc().vertexStride(), fEllipses.count());
2018        VertexWriter verts{helper.vertices()};
2019        if (!verts) {
2020            SkDebugf("Could not allocate vertices\n");
2021            return;
2022        }
2023
2024        // On MSAA, bloat enough to guarantee any pixel that might be touched by the ellipse has
2025        // full sample coverage.
2026        float aaBloat = target->usesMSAASurface() ? SK_ScalarSqrt2 : .5f;
2027
2028        for (const auto& ellipse : fEllipses) {
2029            GrVertexColor color(ellipse.fColor, fWideColor);
2030            SkScalar xRadius = ellipse.fXRadius;
2031            SkScalar yRadius = ellipse.fYRadius;
2032
2033            // Compute the reciprocals of the radii here to save time in the shader
2034            struct { float xOuter, yOuter, xInner, yInner; } invRadii = {
2035                SkScalarInvert(xRadius),
2036                SkScalarInvert(yRadius),
2037                SkScalarInvert(ellipse.fInnerXRadius),
2038                SkScalarInvert(ellipse.fInnerYRadius)
2039            };
2040            SkScalar xMaxOffset = xRadius + aaBloat;
2041            SkScalar yMaxOffset = yRadius + aaBloat;
2042
2043            if (!fStroked) {
2044                // For filled ellipses we map a unit circle in the vertex attributes rather than
2045                // computing an ellipse and modifying that distance, so we normalize to 1
2046                xMaxOffset /= xRadius;
2047                yMaxOffset /= yRadius;
2048            }
2049
2050            // The inner radius in the vertex data must be specified in normalized space.
2051            verts.writeQuad(VertexWriter::TriStripFromRect(
2052                                    ellipse.fDevBounds.makeOutset(aaBloat, aaBloat)),
2053                            color,
2054                            origin_centered_tri_strip(xMaxOffset, yMaxOffset),
2055                            VertexWriter::If(fUseScale, std::max(xRadius, yRadius)),
2056                            invRadii);
2057        }
2058        fMesh = helper.mesh();
2059    }
2060
2061    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2062        if (!fProgramInfo || !fMesh) {
2063            return;
2064        }
2065
2066        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
2067        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
2068        flushState->drawMesh(*fMesh);
2069    }
2070
2071    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
2072        EllipseOp* that = t->cast<EllipseOp>();
2073
2074        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
2075            return CombineResult::kCannotCombine;
2076        }
2077
2078        if (fStroked != that->fStroked) {
2079            return CombineResult::kCannotCombine;
2080        }
2081
2082        if (fHelper.usesLocalCoords() &&
2083            !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords,
2084                                      that->fViewMatrixIfUsingLocalCoords)) {
2085            return CombineResult::kCannotCombine;
2086        }
2087
2088        fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
2089        fWideColor |= that->fWideColor;
2090        return CombineResult::kMerged;
2091    }
2092
2093#if GR_TEST_UTILS
2094    SkString onDumpInfo() const override {
2095        SkString string = SkStringPrintf("Stroked: %d\n", fStroked);
2096        for (const auto& geo : fEllipses) {
2097            string.appendf(
2098                    "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
2099                    "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
2100                    geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
2101                    geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
2102                    geo.fInnerXRadius, geo.fInnerYRadius);
2103        }
2104        string += fHelper.dumpInfo();
2105        return string;
2106    }
2107#endif
2108
2109    struct Ellipse {
2110        SkPMColor4f fColor;
2111        SkScalar fXRadius;
2112        SkScalar fYRadius;
2113        SkScalar fInnerXRadius;
2114        SkScalar fInnerYRadius;
2115        SkRect fDevBounds;
2116    };
2117
2118    SkMatrix fViewMatrixIfUsingLocalCoords;
2119    Helper fHelper;
2120    bool fStroked;
2121    bool fWideColor;
2122    bool fUseScale;
2123    SkSTArray<1, Ellipse, true> fEllipses;
2124
2125    GrSimpleMesh*  fMesh = nullptr;
2126    GrProgramInfo* fProgramInfo = nullptr;
2127
2128    using INHERITED = GrMeshDrawOp;
2129};
2130
2131/////////////////////////////////////////////////////////////////////////////////////////////////
2132
2133class DIEllipseOp : public GrMeshDrawOp {
2134private:
2135    using Helper = GrSimpleMeshDrawOpHelper;
2136
2137    struct DeviceSpaceParams {
2138        SkPoint fCenter;
2139        SkScalar fXRadius;
2140        SkScalar fYRadius;
2141        SkScalar fInnerXRadius;
2142        SkScalar fInnerYRadius;
2143        DIEllipseStyle fStyle;
2144    };
2145
2146public:
2147    DEFINE_OP_CLASS_ID
2148
2149    static GrOp::Owner Make(GrRecordingContext* context,
2150                            GrPaint&& paint,
2151                            const SkMatrix& viewMatrix,
2152                            const SkRect& ellipse,
2153                            const SkStrokeRec& stroke) {
2154        DeviceSpaceParams params;
2155        params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
2156        params.fXRadius = SkScalarHalf(ellipse.width());
2157        params.fYRadius = SkScalarHalf(ellipse.height());
2158
2159        SkStrokeRec::Style style = stroke.getStyle();
2160        params.fStyle = (SkStrokeRec::kStroke_Style == style)
2161                                ? DIEllipseStyle::kStroke
2162                                : (SkStrokeRec::kHairline_Style == style)
2163                                          ? DIEllipseStyle::kHairline
2164                                          : DIEllipseStyle::kFill;
2165
2166        params.fInnerXRadius = 0;
2167        params.fInnerYRadius = 0;
2168        if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) {
2169            SkScalar strokeWidth = stroke.getWidth();
2170
2171            if (SkScalarNearlyZero(strokeWidth)) {
2172                strokeWidth = SK_ScalarHalf;
2173            } else {
2174                strokeWidth *= SK_ScalarHalf;
2175            }
2176
2177            // we only handle thick strokes for near-circular ellipses
2178            if (strokeWidth > SK_ScalarHalf &&
2179                (SK_ScalarHalf * params.fXRadius > params.fYRadius ||
2180                 SK_ScalarHalf * params.fYRadius > params.fXRadius)) {
2181                return nullptr;
2182            }
2183
2184            // we don't handle it if curvature of the stroke is less than curvature of the ellipse
2185            if (strokeWidth * (params.fYRadius * params.fYRadius) <
2186                (strokeWidth * strokeWidth) * params.fXRadius) {
2187                return nullptr;
2188            }
2189            if (strokeWidth * (params.fXRadius * params.fXRadius) <
2190                (strokeWidth * strokeWidth) * params.fYRadius) {
2191                return nullptr;
2192            }
2193
2194            // set inner radius (if needed)
2195            if (SkStrokeRec::kStroke_Style == style) {
2196                params.fInnerXRadius = params.fXRadius - strokeWidth;
2197                params.fInnerYRadius = params.fYRadius - strokeWidth;
2198            }
2199
2200            params.fXRadius += strokeWidth;
2201            params.fYRadius += strokeWidth;
2202        }
2203
2204        // For large ovals with low precision floats, we fall back to the path renderer.
2205        // To compute the AA at the edge we divide by the gradient, which is clamped to a
2206        // minimum value to avoid divides by zero. With large ovals and low precision this
2207        // leads to blurring at the edge of the oval.
2208        const SkScalar kMaxOvalRadius = 16384;
2209        if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
2210            (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
2211            return nullptr;
2212        }
2213
2214        if (DIEllipseStyle::kStroke == params.fStyle &&
2215            (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) {
2216            params.fStyle = DIEllipseStyle::kFill;
2217        }
2218        return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), params, viewMatrix);
2219    }
2220
2221    DIEllipseOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
2222                const DeviceSpaceParams& params, const SkMatrix& viewMatrix)
2223            : INHERITED(ClassID())
2224            , fHelper(processorSet, GrAAType::kCoverage)
2225            , fUseScale(false) {
2226        // This expands the outer rect so that after CTM we end up with a half-pixel border
2227        SkScalar a = viewMatrix[SkMatrix::kMScaleX];
2228        SkScalar b = viewMatrix[SkMatrix::kMSkewX];
2229        SkScalar c = viewMatrix[SkMatrix::kMSkewY];
2230        SkScalar d = viewMatrix[SkMatrix::kMScaleY];
2231        SkScalar geoDx = 1.f / SkScalarSqrt(a * a + c * c);
2232        SkScalar geoDy = 1.f / SkScalarSqrt(b * b + d * d);
2233
2234        fEllipses.emplace_back(
2235                Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius,
2236                        params.fInnerYRadius, geoDx, geoDy, params.fStyle,
2237                        SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius,
2238                                         params.fCenter.fY - params.fYRadius,
2239                                         params.fCenter.fX + params.fXRadius,
2240                                         params.fCenter.fY + params.fYRadius)});
2241        this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes,
2242                                   IsHairline::kNo);
2243    }
2244
2245    const char* name() const override { return "DIEllipseOp"; }
2246
2247    void visitProxies(const GrVisitProxyFunc& func) const override {
2248        if (fProgramInfo) {
2249            fProgramInfo->visitFPProxies(func);
2250        } else {
2251            fHelper.visitProxies(func);
2252        }
2253    }
2254
2255    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
2256                                      GrClampType clampType) override {
2257        fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
2258                    !caps.shaderCaps()->hasLowFragmentPrecision();
2259        SkPMColor4f* color = &fEllipses.front().fColor;
2260        return fHelper.finalizeProcessors(caps, clip, clampType,
2261                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
2262                                          &fWideColor);
2263    }
2264
2265    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2266
2267private:
2268    GrProgramInfo* programInfo() override { return fProgramInfo; }
2269
2270    void onCreateProgramInfo(const GrCaps* caps,
2271                             SkArenaAlloc* arena,
2272                             const GrSurfaceProxyView& writeView,
2273                             bool usesMSAASurface,
2274                             GrAppliedClip&& appliedClip,
2275                             const GrDstProxyView& dstProxyView,
2276                             GrXferBarrierFlags renderPassXferBarriers,
2277                             GrLoadOp colorLoadOp) override {
2278        GrGeometryProcessor* gp = DIEllipseGeometryProcessor::Make(arena, fWideColor, fUseScale,
2279                                                                   this->viewMatrix(),
2280                                                                   this->style());
2281
2282        fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface,
2283                                                 std::move(appliedClip), dstProxyView, gp,
2284                                                 GrPrimitiveType::kTriangles,
2285                                                 renderPassXferBarriers, colorLoadOp);
2286    }
2287
2288    void onPrepareDraws(GrMeshDrawTarget* target) override {
2289        if (!fProgramInfo) {
2290            this->createProgramInfo(target);
2291        }
2292
2293        QuadHelper helper(target, fProgramInfo->geomProc().vertexStride(), fEllipses.count());
2294        VertexWriter verts{helper.vertices()};
2295        if (!verts) {
2296            return;
2297        }
2298
2299        for (const auto& ellipse : fEllipses) {
2300            GrVertexColor color(ellipse.fColor, fWideColor);
2301            SkScalar xRadius = ellipse.fXRadius;
2302            SkScalar yRadius = ellipse.fYRadius;
2303
2304            // On MSAA, bloat enough to guarantee any pixel that might be touched by the ellipse has
2305            // full sample coverage.
2306            float aaBloat = target->usesMSAASurface() ? SK_ScalarSqrt2 : .5f;
2307            SkRect drawBounds = ellipse.fBounds.makeOutset(ellipse.fGeoDx * aaBloat,
2308                                                           ellipse.fGeoDy * aaBloat);
2309
2310            // Normalize the "outer radius" coordinates within drawBounds so that the outer edge
2311            // occurs at x^2 + y^2 == 1.
2312            float outerCoordX = drawBounds.width() / (xRadius * 2);
2313            float outerCoordY = drawBounds.height() / (yRadius * 2);
2314
2315            // By default, constructed so that inner coord is (0, 0) for all points
2316            float innerCoordX = 0;
2317            float innerCoordY = 0;
2318
2319            // ... unless we're stroked. Then normalize the "inner radius" coordinates within
2320            // drawBounds so that the inner edge occurs at x2^2 + y2^2 == 1.
2321            if (DIEllipseStyle::kStroke == this->style()) {
2322                innerCoordX = drawBounds.width() / (ellipse.fInnerXRadius * 2);
2323                innerCoordY = drawBounds.height() / (ellipse.fInnerYRadius * 2);
2324            }
2325
2326            verts.writeQuad(VertexWriter::TriStripFromRect(drawBounds),
2327                            color,
2328                            origin_centered_tri_strip(outerCoordX, outerCoordY),
2329                            VertexWriter::If(fUseScale, std::max(xRadius, yRadius)),
2330                            origin_centered_tri_strip(innerCoordX, innerCoordY));
2331        }
2332        fMesh = helper.mesh();
2333    }
2334
2335    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2336        if (!fProgramInfo || !fMesh) {
2337            return;
2338        }
2339
2340        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
2341        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
2342        flushState->drawMesh(*fMesh);
2343    }
2344
2345    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
2346        DIEllipseOp* that = t->cast<DIEllipseOp>();
2347        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
2348            return CombineResult::kCannotCombine;
2349        }
2350
2351        if (this->style() != that->style()) {
2352            return CombineResult::kCannotCombine;
2353        }
2354
2355        // TODO rewrite to allow positioning on CPU
2356        if (!SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) {
2357            return CombineResult::kCannotCombine;
2358        }
2359
2360        fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
2361        fWideColor |= that->fWideColor;
2362        return CombineResult::kMerged;
2363    }
2364
2365#if GR_TEST_UTILS
2366    SkString onDumpInfo() const override {
2367        SkString string;
2368        for (const auto& geo : fEllipses) {
2369            string.appendf(
2370                    "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, "
2371                    "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, "
2372                    "GeoDY: %.2f\n",
2373                    geo.fColor.toBytes_RGBA(), geo.fBounds.fLeft, geo.fBounds.fTop,
2374                    geo.fBounds.fRight, geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius,
2375                    geo.fInnerXRadius, geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy);
2376        }
2377        string += fHelper.dumpInfo();
2378        return string;
2379    }
2380#endif
2381
2382    const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; }
2383    DIEllipseStyle style() const { return fEllipses[0].fStyle; }
2384
2385    struct Ellipse {
2386        SkMatrix fViewMatrix;
2387        SkPMColor4f fColor;
2388        SkScalar fXRadius;
2389        SkScalar fYRadius;
2390        SkScalar fInnerXRadius;
2391        SkScalar fInnerYRadius;
2392        SkScalar fGeoDx;
2393        SkScalar fGeoDy;
2394        DIEllipseStyle fStyle;
2395        SkRect fBounds;
2396    };
2397
2398    Helper fHelper;
2399    bool fWideColor;
2400    bool fUseScale;
2401    SkSTArray<1, Ellipse, true> fEllipses;
2402
2403    GrSimpleMesh*  fMesh = nullptr;
2404    GrProgramInfo* fProgramInfo = nullptr;
2405
2406    using INHERITED = GrMeshDrawOp;
2407};
2408
2409///////////////////////////////////////////////////////////////////////////////
2410
2411// We have three possible cases for geometry for a roundrect.
2412//
2413// In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch:
2414//    ____________
2415//   |_|________|_|
2416//   | |        | |
2417//   | |        | |
2418//   | |        | |
2419//   |_|________|_|
2420//   |_|________|_|
2421//
2422// For strokes, we don't draw the center quad.
2423//
2424// For circular roundrects, in the case where the stroke width is greater than twice
2425// the corner radius (overstroke), we add additional geometry to mark out the rectangle
2426// in the center. The shared vertices are duplicated so we can set a different outer radius
2427// for the fill calculation.
2428//    ____________
2429//   |_|________|_|
2430//   | |\ ____ /| |
2431//   | | |    | | |
2432//   | | |____| | |
2433//   |_|/______\|_|
2434//   |_|________|_|
2435//
2436// We don't draw the center quad from the fill rect in this case.
2437//
2438// For filled rrects that need to provide a distance vector we resuse the overstroke
2439// geometry but make the inner rect degenerate (either a point or a horizontal or
2440// vertical line).
2441
2442static const uint16_t gOverstrokeRRectIndices[] = {
2443        // clang-format off
2444        // overstroke quads
2445        // we place this at the beginning so that we can skip these indices when rendering normally
2446        16, 17, 19, 16, 19, 18,
2447        19, 17, 23, 19, 23, 21,
2448        21, 23, 22, 21, 22, 20,
2449        22, 16, 18, 22, 18, 20,
2450
2451        // corners
2452        0, 1, 5, 0, 5, 4,
2453        2, 3, 7, 2, 7, 6,
2454        8, 9, 13, 8, 13, 12,
2455        10, 11, 15, 10, 15, 14,
2456
2457        // edges
2458        1, 2, 6, 1, 6, 5,
2459        4, 5, 9, 4, 9, 8,
2460        6, 7, 11, 6, 11, 10,
2461        9, 10, 14, 9, 14, 13,
2462
2463        // center
2464        // we place this at the end so that we can ignore these indices when not rendering as filled
2465        5, 6, 10, 5, 10, 9,
2466        // clang-format on
2467};
2468
2469// fill and standard stroke indices skip the overstroke "ring"
2470static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4;
2471
2472// overstroke count is arraysize minus the center indices
2473static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6;
2474// fill count skips overstroke indices and includes center
2475static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
2476// stroke count is fill count minus center indices
2477static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6;
2478static const int kVertsPerStandardRRect = 16;
2479static const int kVertsPerOverstrokeRRect = 24;
2480
2481enum RRectType {
2482    kFill_RRectType,
2483    kStroke_RRectType,
2484    kOverstroke_RRectType,
2485};
2486
2487static int rrect_type_to_vert_count(RRectType type) {
2488    switch (type) {
2489        case kFill_RRectType:
2490        case kStroke_RRectType:
2491            return kVertsPerStandardRRect;
2492        case kOverstroke_RRectType:
2493            return kVertsPerOverstrokeRRect;
2494    }
2495    SK_ABORT("Invalid type");
2496}
2497
2498static int rrect_type_to_index_count(RRectType type) {
2499    switch (type) {
2500        case kFill_RRectType:
2501            return kIndicesPerFillRRect;
2502        case kStroke_RRectType:
2503            return kIndicesPerStrokeRRect;
2504        case kOverstroke_RRectType:
2505            return kIndicesPerOverstrokeRRect;
2506    }
2507    SK_ABORT("Invalid type");
2508}
2509
2510static const uint16_t* rrect_type_to_indices(RRectType type) {
2511    switch (type) {
2512        case kFill_RRectType:
2513        case kStroke_RRectType:
2514            return gStandardRRectIndices;
2515        case kOverstroke_RRectType:
2516            return gOverstrokeRRectIndices;
2517    }
2518    SK_ABORT("Invalid type");
2519}
2520
2521///////////////////////////////////////////////////////////////////////////////////////////////////
2522
2523// For distance computations in the interior of filled rrects we:
2524//
2525//   add a interior degenerate (point or line) rect
2526//   each vertex of that rect gets -outerRad as its radius
2527//      this makes the computation of the distance to the outer edge be negative
2528//      negative values are caught and then handled differently in the GP's onEmitCode
2529//   each vertex is also given the normalized x & y distance from the interior rect's edge
2530//      the GP takes the min of those depths +1 to get the normalized distance to the outer edge
2531
2532class CircularRRectOp : public GrMeshDrawOp {
2533private:
2534    using Helper = GrSimpleMeshDrawOpHelper;
2535
2536public:
2537    DEFINE_OP_CLASS_ID
2538
2539    // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
2540    // whether the rrect is only stroked or stroked and filled.
2541    static GrOp::Owner Make(GrRecordingContext* context,
2542                            GrPaint&& paint,
2543                            const SkMatrix& viewMatrix,
2544                            const SkRect& devRect,
2545                            float devRadius,
2546                            float devStrokeWidth,
2547                            bool strokeOnly) {
2548        return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix,
2549                                                      devRect, devRadius,
2550                                                      devStrokeWidth, strokeOnly);
2551    }
2552    CircularRRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
2553                    const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius,
2554                    float devStrokeWidth, bool strokeOnly)
2555            : INHERITED(ClassID())
2556            , fViewMatrixIfUsingLocalCoords(viewMatrix)
2557            , fHelper(processorSet, GrAAType::kCoverage) {
2558        SkRect bounds = devRect;
2559        SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
2560        SkScalar innerRadius = 0.0f;
2561        SkScalar outerRadius = devRadius;
2562        SkScalar halfWidth = 0;
2563        RRectType type = kFill_RRectType;
2564        if (devStrokeWidth > 0) {
2565            if (SkScalarNearlyZero(devStrokeWidth)) {
2566                halfWidth = SK_ScalarHalf;
2567            } else {
2568                halfWidth = SkScalarHalf(devStrokeWidth);
2569            }
2570
2571            if (strokeOnly) {
2572                // Outset stroke by 1/4 pixel
2573                devStrokeWidth += 0.25f;
2574                // If stroke is greater than width or height, this is still a fill
2575                // Otherwise we compute stroke params
2576                if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) {
2577                    innerRadius = devRadius - halfWidth;
2578                    type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
2579                }
2580            }
2581            outerRadius += halfWidth;
2582            bounds.outset(halfWidth, halfWidth);
2583        }
2584
2585        // The radii are outset for two reasons. First, it allows the shader to simply perform
2586        // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
2587        // Second, the outer radius is used to compute the verts of the bounding box that is
2588        // rendered and the outset ensures the box will cover all partially covered by the rrect
2589        // corners.
2590        outerRadius += SK_ScalarHalf;
2591        innerRadius -= SK_ScalarHalf;
2592
2593        this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo);
2594
2595        // Expand the rect for aa to generate correct vertices.
2596        bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
2597
2598        fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type});
2599        fVertCount = rrect_type_to_vert_count(type);
2600        fIndexCount = rrect_type_to_index_count(type);
2601        fAllFill = (kFill_RRectType == type);
2602    }
2603
2604    const char* name() const override { return "CircularRRectOp"; }
2605
2606    void visitProxies(const GrVisitProxyFunc& func) const override {
2607        if (fProgramInfo) {
2608            fProgramInfo->visitFPProxies(func);
2609        } else {
2610            fHelper.visitProxies(func);
2611        }
2612    }
2613
2614    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
2615                                      GrClampType clampType) override {
2616        SkPMColor4f* color = &fRRects.front().fColor;
2617        return fHelper.finalizeProcessors(caps, clip, clampType,
2618                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
2619                                          &fWideColor);
2620    }
2621
2622    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2623
2624private:
2625    static void FillInOverstrokeVerts(VertexWriter& verts, const SkRect& bounds, SkScalar smInset,
2626                                      SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius,
2627                                      SkScalar innerRadius, const GrVertexColor& color) {
2628        SkASSERT(smInset < bigInset);
2629
2630        // TL
2631        verts << (bounds.fLeft + smInset) << (bounds.fTop + smInset)
2632              << color
2633              << xOffset << 0.0f
2634              << outerRadius << innerRadius;
2635
2636        // TR
2637        verts << (bounds.fRight - smInset) << (bounds.fTop + smInset)
2638              << color
2639              << xOffset << 0.0f
2640              << outerRadius << innerRadius;
2641
2642        verts << (bounds.fLeft + bigInset) << (bounds.fTop + bigInset)
2643              << color
2644              << 0.0f << 0.0f
2645              << outerRadius << innerRadius;
2646
2647        verts << (bounds.fRight - bigInset) << (bounds.fTop + bigInset)
2648              << color
2649              << 0.0f << 0.0f
2650              << outerRadius << innerRadius;
2651
2652        verts << (bounds.fLeft + bigInset) << (bounds.fBottom - bigInset)
2653              << color
2654              << 0.0f << 0.0f
2655              << outerRadius << innerRadius;
2656
2657        verts << (bounds.fRight - bigInset) << (bounds.fBottom - bigInset)
2658              << color
2659              << 0.0f << 0.0f
2660              << outerRadius << innerRadius;
2661
2662        // BL
2663        verts << (bounds.fLeft + smInset) << (bounds.fBottom - smInset)
2664              << color
2665              << xOffset << 0.0f
2666              << outerRadius << innerRadius;
2667
2668        // BR
2669        verts << (bounds.fRight - smInset) << (bounds.fBottom - smInset)
2670              << color
2671              << xOffset << 0.0f
2672              << outerRadius << innerRadius;
2673    }
2674
2675    GrProgramInfo* programInfo() override { return fProgramInfo; }
2676
2677    void onCreateProgramInfo(const GrCaps* caps,
2678                             SkArenaAlloc* arena,
2679                             const GrSurfaceProxyView& writeView,
2680                             bool usesMSAASurface,
2681                             GrAppliedClip&& appliedClip,
2682                             const GrDstProxyView& dstProxyView,
2683                             GrXferBarrierFlags renderPassXferBarriers,
2684                             GrLoadOp colorLoadOp) override {
2685        SkASSERT(!usesMSAASurface);
2686
2687        // Invert the view matrix as a local matrix (if any other processors require coords).
2688        SkMatrix localMatrix;
2689        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
2690            return;
2691        }
2692
2693        GrGeometryProcessor* gp = CircleGeometryProcessor::Make(arena, !fAllFill,
2694                                                                false, false, false, false,
2695                                                                fWideColor, localMatrix);
2696
2697        fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface,
2698                                                 std::move(appliedClip), dstProxyView, gp,
2699                                                 GrPrimitiveType::kTriangles,
2700                                                 renderPassXferBarriers, colorLoadOp);
2701    }
2702
2703    void onPrepareDraws(GrMeshDrawTarget* target) override {
2704        if (!fProgramInfo) {
2705            this->createProgramInfo(target);
2706            if (!fProgramInfo) {
2707                return;
2708            }
2709        }
2710
2711        sk_sp<const GrBuffer> vertexBuffer;
2712        int firstVertex;
2713
2714        VertexWriter verts{target->makeVertexSpace(fProgramInfo->geomProc().vertexStride(),
2715                                                     fVertCount, &vertexBuffer, &firstVertex)};
2716        if (!verts) {
2717            SkDebugf("Could not allocate vertices\n");
2718            return;
2719        }
2720
2721        sk_sp<const GrBuffer> indexBuffer;
2722        int firstIndex = 0;
2723        uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
2724        if (!indices) {
2725            SkDebugf("Could not allocate indices\n");
2726            return;
2727        }
2728
2729        int currStartVertex = 0;
2730        for (const auto& rrect : fRRects) {
2731            GrVertexColor color(rrect.fColor, fWideColor);
2732            SkScalar outerRadius = rrect.fOuterRadius;
2733            const SkRect& bounds = rrect.fDevBounds;
2734
2735            SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius,
2736                                   bounds.fBottom - outerRadius, bounds.fBottom};
2737
2738            SkScalar yOuterRadii[4] = {-1, 0, 0, 1};
2739            // The inner radius in the vertex data must be specified in normalized space.
2740            // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
2741            SkScalar innerRadius = rrect.fType != kFill_RRectType
2742                                           ? rrect.fInnerRadius / rrect.fOuterRadius
2743                                           : -1.0f / rrect.fOuterRadius;
2744            for (int i = 0; i < 4; ++i) {
2745                verts << bounds.fLeft << yCoords[i]
2746                      << color
2747                      << -1.0f << yOuterRadii[i]
2748                      << outerRadius << innerRadius;
2749
2750                verts << (bounds.fLeft + outerRadius) << yCoords[i]
2751                      << color
2752                      << 0.0f << yOuterRadii[i]
2753                      << outerRadius << innerRadius;
2754
2755                verts << (bounds.fRight - outerRadius) << yCoords[i]
2756                      << color
2757                      << 0.0f << yOuterRadii[i]
2758                      << outerRadius << innerRadius;
2759
2760                verts << bounds.fRight << yCoords[i]
2761                      << color
2762                      << 1.0f << yOuterRadii[i]
2763                      << outerRadius << innerRadius;
2764            }
2765            // Add the additional vertices for overstroked rrects.
2766            // Effectively this is an additional stroked rrect, with its
2767            // outer radius = outerRadius - innerRadius, and inner radius = 0.
2768            // This will give us correct AA in the center and the correct
2769            // distance to the outer edge.
2770            //
2771            // Also, the outer offset is a constant vector pointing to the right, which
2772            // guarantees that the distance value along the outer rectangle is constant.
2773            if (kOverstroke_RRectType == rrect.fType) {
2774                SkASSERT(rrect.fInnerRadius <= 0.0f);
2775
2776                SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius;
2777                // this is the normalized distance from the outer rectangle of this
2778                // geometry to the outer edge
2779                SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius;
2780
2781                FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
2782                                      overstrokeOuterRadius, 0.0f, color);
2783            }
2784
2785            const uint16_t* primIndices = rrect_type_to_indices(rrect.fType);
2786            const int primIndexCount = rrect_type_to_index_count(rrect.fType);
2787            for (int i = 0; i < primIndexCount; ++i) {
2788                *indices++ = primIndices[i] + currStartVertex;
2789            }
2790
2791            currStartVertex += rrect_type_to_vert_count(rrect.fType);
2792        }
2793
2794        fMesh = target->allocMesh();
2795        fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
2796                          GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex);
2797    }
2798
2799    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2800        if (!fProgramInfo || !fMesh) {
2801            return;
2802        }
2803
2804        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
2805        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
2806        flushState->drawMesh(*fMesh);
2807    }
2808
2809    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
2810        CircularRRectOp* that = t->cast<CircularRRectOp>();
2811
2812        // can only represent 65535 unique vertices with 16-bit indices
2813        if (fVertCount + that->fVertCount > 65536) {
2814            return CombineResult::kCannotCombine;
2815        }
2816
2817        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
2818            return CombineResult::kCannotCombine;
2819        }
2820
2821        if (fHelper.usesLocalCoords() &&
2822            !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords,
2823                                      that->fViewMatrixIfUsingLocalCoords)) {
2824            return CombineResult::kCannotCombine;
2825        }
2826
2827        fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
2828        fVertCount += that->fVertCount;
2829        fIndexCount += that->fIndexCount;
2830        fAllFill = fAllFill && that->fAllFill;
2831        fWideColor = fWideColor || that->fWideColor;
2832        return CombineResult::kMerged;
2833    }
2834
2835#if GR_TEST_UTILS
2836    SkString onDumpInfo() const override {
2837        SkString string;
2838        for (int i = 0; i < fRRects.count(); ++i) {
2839            string.appendf(
2840                    "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
2841                    "InnerRad: %.2f, OuterRad: %.2f\n",
2842                    fRRects[i].fColor.toBytes_RGBA(), fRRects[i].fDevBounds.fLeft,
2843                    fRRects[i].fDevBounds.fTop, fRRects[i].fDevBounds.fRight,
2844                    fRRects[i].fDevBounds.fBottom, fRRects[i].fInnerRadius,
2845                    fRRects[i].fOuterRadius);
2846        }
2847        string += fHelper.dumpInfo();
2848        return string;
2849    }
2850#endif
2851
2852    struct RRect {
2853        SkPMColor4f fColor;
2854        SkScalar fInnerRadius;
2855        SkScalar fOuterRadius;
2856        SkRect fDevBounds;
2857        RRectType fType;
2858    };
2859
2860    SkMatrix fViewMatrixIfUsingLocalCoords;
2861    Helper fHelper;
2862    int fVertCount;
2863    int fIndexCount;
2864    bool fAllFill;
2865    bool fWideColor;
2866    SkSTArray<1, RRect, true> fRRects;
2867
2868    GrSimpleMesh*  fMesh = nullptr;
2869    GrProgramInfo* fProgramInfo = nullptr;
2870
2871    using INHERITED = GrMeshDrawOp;
2872};
2873
2874static const int kNumRRectsInIndexBuffer = 256;
2875
2876GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2877GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
2878static sk_sp<const GrBuffer> get_rrect_index_buffer(RRectType type,
2879                                                    GrResourceProvider* resourceProvider) {
2880    GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2881    GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
2882    switch (type) {
2883        case kFill_RRectType:
2884            return resourceProvider->findOrCreatePatternedIndexBuffer(
2885                    gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer,
2886                    kVertsPerStandardRRect, gRRectOnlyIndexBufferKey);
2887        case kStroke_RRectType:
2888            return resourceProvider->findOrCreatePatternedIndexBuffer(
2889                    gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer,
2890                    kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey);
2891        default:
2892            SkASSERT(false);
2893            return nullptr;
2894    }
2895}
2896
2897class EllipticalRRectOp : public GrMeshDrawOp {
2898private:
2899    using Helper = GrSimpleMeshDrawOpHelper;
2900
2901public:
2902    DEFINE_OP_CLASS_ID
2903
2904    // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
2905    // whether the rrect is only stroked or stroked and filled.
2906    static GrOp::Owner Make(GrRecordingContext* context,
2907                            GrPaint&& paint,
2908                            const SkMatrix& viewMatrix,
2909                            const SkRect& devRect,
2910                            float devXRadius,
2911                            float devYRadius,
2912                            SkVector devStrokeWidths,
2913                            bool strokeOnly) {
2914        SkASSERT(devXRadius >= 0.5 || strokeOnly);
2915        SkASSERT(devYRadius >= 0.5 || strokeOnly);
2916        SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0));
2917        SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0));
2918        if (devStrokeWidths.fX > 0) {
2919            if (SkScalarNearlyZero(devStrokeWidths.length())) {
2920                devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf);
2921            } else {
2922                devStrokeWidths.scale(SK_ScalarHalf);
2923            }
2924
2925            // we only handle thick strokes for near-circular ellipses
2926            if (devStrokeWidths.length() > SK_ScalarHalf &&
2927                (SK_ScalarHalf * devXRadius > devYRadius ||
2928                 SK_ScalarHalf * devYRadius > devXRadius)) {
2929                return nullptr;
2930            }
2931
2932            // we don't handle it if curvature of the stroke is less than curvature of the ellipse
2933            if (devStrokeWidths.fX * (devYRadius * devYRadius) <
2934                (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) {
2935                return nullptr;
2936            }
2937            if (devStrokeWidths.fY * (devXRadius * devXRadius) <
2938                (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) {
2939                return nullptr;
2940            }
2941        }
2942        return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint),
2943                                                        viewMatrix, devRect,
2944                                                        devXRadius, devYRadius, devStrokeWidths,
2945                                                        strokeOnly);
2946    }
2947
2948    EllipticalRRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
2949                      const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius,
2950                      float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly)
2951            : INHERITED(ClassID())
2952            , fHelper(processorSet, GrAAType::kCoverage)
2953            , fUseScale(false) {
2954        SkScalar innerXRadius = 0.0f;
2955        SkScalar innerYRadius = 0.0f;
2956        SkRect bounds = devRect;
2957        bool stroked = false;
2958        if (devStrokeHalfWidths.fX > 0) {
2959            // this is legit only if scale & translation (which should be the case at the moment)
2960            if (strokeOnly) {
2961                innerXRadius = devXRadius - devStrokeHalfWidths.fX;
2962                innerYRadius = devYRadius - devStrokeHalfWidths.fY;
2963                stroked = (innerXRadius >= 0 && innerYRadius >= 0);
2964            }
2965
2966            devXRadius += devStrokeHalfWidths.fX;
2967            devYRadius += devStrokeHalfWidths.fY;
2968            bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY);
2969        }
2970
2971        fStroked = stroked;
2972        fViewMatrixIfUsingLocalCoords = viewMatrix;
2973        this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo);
2974        fRRects.emplace_back(
2975                RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
2976    }
2977
2978    const char* name() const override { return "EllipticalRRectOp"; }
2979
2980    void visitProxies(const GrVisitProxyFunc& func) const override {
2981        if (fProgramInfo) {
2982            fProgramInfo->visitFPProxies(func);
2983        } else {
2984            fHelper.visitProxies(func);
2985        }
2986    }
2987
2988    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
2989                                      GrClampType clampType) override {
2990        fUseScale = !caps.shaderCaps()->floatIs32Bits();
2991        SkPMColor4f* color = &fRRects.front().fColor;
2992        return fHelper.finalizeProcessors(caps, clip, clampType,
2993                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
2994                                          &fWideColor);
2995    }
2996
2997    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2998
2999private:
3000    GrProgramInfo* programInfo() override { return fProgramInfo; }
3001
3002    void onCreateProgramInfo(const GrCaps* caps,
3003                             SkArenaAlloc* arena,
3004                             const GrSurfaceProxyView& writeView,
3005                             bool usesMSAASurface,
3006                             GrAppliedClip&& appliedClip,
3007                             const GrDstProxyView& dstProxyView,
3008                             GrXferBarrierFlags renderPassXferBarriers,
3009                             GrLoadOp colorLoadOp) override {
3010        SkMatrix localMatrix;
3011        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
3012            return;
3013        }
3014
3015        GrGeometryProcessor* gp = EllipseGeometryProcessor::Make(arena, fStroked, fWideColor,
3016                                                                 fUseScale, localMatrix);
3017
3018        fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface,
3019                                                 std::move(appliedClip), dstProxyView, gp,
3020                                                 GrPrimitiveType::kTriangles,
3021                                                 renderPassXferBarriers, colorLoadOp);
3022    }
3023
3024    void onPrepareDraws(GrMeshDrawTarget* target) override {
3025        if (!fProgramInfo) {
3026            this->createProgramInfo(target);
3027            if (!fProgramInfo) {
3028                return;
3029            }
3030        }
3031
3032        // drop out the middle quad if we're stroked
3033        int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
3034        sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer(
3035                fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider());
3036
3037        if (!indexBuffer) {
3038            SkDebugf("Could not allocate indices\n");
3039            return;
3040        }
3041        PatternHelper helper(target, GrPrimitiveType::kTriangles,
3042                             fProgramInfo->geomProc().vertexStride(),
3043                             std::move(indexBuffer), kVertsPerStandardRRect, indicesPerInstance,
3044                             fRRects.count(), kNumRRectsInIndexBuffer);
3045        VertexWriter verts{helper.vertices()};
3046        if (!verts) {
3047            SkDebugf("Could not allocate vertices\n");
3048            return;
3049        }
3050
3051        for (const auto& rrect : fRRects) {
3052            GrVertexColor color(rrect.fColor, fWideColor);
3053            // Compute the reciprocals of the radii here to save time in the shader
3054            float reciprocalRadii[4] = {
3055                SkScalarInvert(rrect.fXRadius),
3056                SkScalarInvert(rrect.fYRadius),
3057                SkScalarInvert(rrect.fInnerXRadius),
3058                SkScalarInvert(rrect.fInnerYRadius)
3059            };
3060
3061            // If the stroke width is exactly double the radius, the inner radii will be zero.
3062            // Pin to a large value, to avoid infinities in the shader. crbug.com/1139750
3063            reciprocalRadii[2] = std::min(reciprocalRadii[2], 1e6f);
3064            reciprocalRadii[3] = std::min(reciprocalRadii[3], 1e6f);
3065
3066            // On MSAA, bloat enough to guarantee any pixel that might be touched by the rrect has
3067            // full sample coverage.
3068            float aaBloat = target->usesMSAASurface() ? SK_ScalarSqrt2 : .5f;
3069
3070            // Extend out the radii to antialias.
3071            SkScalar xOuterRadius = rrect.fXRadius + aaBloat;
3072            SkScalar yOuterRadius = rrect.fYRadius + aaBloat;
3073
3074            SkScalar xMaxOffset = xOuterRadius;
3075            SkScalar yMaxOffset = yOuterRadius;
3076            if (!fStroked) {
3077                // For filled rrects we map a unit circle in the vertex attributes rather than
3078                // computing an ellipse and modifying that distance, so we normalize to 1.
3079                xMaxOffset /= rrect.fXRadius;
3080                yMaxOffset /= rrect.fYRadius;
3081            }
3082
3083            const SkRect& bounds = rrect.fDevBounds.makeOutset(aaBloat, aaBloat);
3084
3085            SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius,
3086                                   bounds.fBottom - yOuterRadius, bounds.fBottom};
3087            SkScalar yOuterOffsets[4] = {yMaxOffset,
3088                                         SK_ScalarNearlyZero,  // we're using inversesqrt() in
3089                                                               // shader, so can't be exactly 0
3090                                         SK_ScalarNearlyZero, yMaxOffset};
3091
3092            auto maybeScale = VertexWriter::If(fUseScale, std::max(rrect.fXRadius, rrect.fYRadius));
3093            for (int i = 0; i < 4; ++i) {
3094                verts << bounds.fLeft << yCoords[i]
3095                      << color
3096                      << xMaxOffset << yOuterOffsets[i]
3097                      << maybeScale
3098                      << reciprocalRadii;
3099
3100                verts << (bounds.fLeft + xOuterRadius) << yCoords[i]
3101                      << color
3102                      << SK_ScalarNearlyZero << yOuterOffsets[i]
3103                      << maybeScale
3104                      << reciprocalRadii;
3105
3106                verts << (bounds.fRight - xOuterRadius) << yCoords[i]
3107                      << color
3108                      << SK_ScalarNearlyZero << yOuterOffsets[i]
3109                      << maybeScale
3110                      << reciprocalRadii;
3111
3112                verts << bounds.fRight << yCoords[i]
3113                      << color
3114                      << xMaxOffset << yOuterOffsets[i]
3115                      << maybeScale
3116                      << reciprocalRadii;
3117            }
3118        }
3119        fMesh = helper.mesh();
3120    }
3121
3122    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
3123        if (!fProgramInfo || !fMesh) {
3124            return;
3125        }
3126
3127        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
3128        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
3129        flushState->drawMesh(*fMesh);
3130    }
3131
3132    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
3133        EllipticalRRectOp* that = t->cast<EllipticalRRectOp>();
3134
3135        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
3136            return CombineResult::kCannotCombine;
3137        }
3138
3139        if (fStroked != that->fStroked) {
3140            return CombineResult::kCannotCombine;
3141        }
3142
3143        if (fHelper.usesLocalCoords() &&
3144            !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords,
3145                                      that->fViewMatrixIfUsingLocalCoords)) {
3146            return CombineResult::kCannotCombine;
3147        }
3148
3149        fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
3150        fWideColor = fWideColor || that->fWideColor;
3151        return CombineResult::kMerged;
3152    }
3153
3154#if GR_TEST_UTILS
3155    SkString onDumpInfo() const override {
3156        SkString string = SkStringPrintf("Stroked: %d\n", fStroked);
3157        for (const auto& geo : fRRects) {
3158            string.appendf(
3159                    "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
3160                    "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
3161                    geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
3162                    geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
3163                    geo.fInnerXRadius, geo.fInnerYRadius);
3164        }
3165        string += fHelper.dumpInfo();
3166        return string;
3167    }
3168#endif
3169
3170    struct RRect {
3171        SkPMColor4f fColor;
3172        SkScalar fXRadius;
3173        SkScalar fYRadius;
3174        SkScalar fInnerXRadius;
3175        SkScalar fInnerYRadius;
3176        SkRect fDevBounds;
3177    };
3178
3179    SkMatrix fViewMatrixIfUsingLocalCoords;
3180    Helper fHelper;
3181    bool fStroked;
3182    bool fWideColor;
3183    bool fUseScale;
3184    SkSTArray<1, RRect, true> fRRects;
3185
3186    GrSimpleMesh*  fMesh = nullptr;
3187    GrProgramInfo* fProgramInfo = nullptr;
3188
3189    using INHERITED = GrMeshDrawOp;
3190};
3191
3192GrOp::Owner GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context,
3193                                                 GrPaint&& paint,
3194                                                 const SkMatrix& viewMatrix,
3195                                                 const SkRRect& rrect,
3196                                                 const SkStrokeRec& stroke,
3197                                                 const GrShaderCaps* shaderCaps) {
3198    SkASSERT(viewMatrix.rectStaysRect());
3199    SkASSERT(viewMatrix.isSimilarity());
3200    SkASSERT(rrect.isSimple());
3201    SkASSERT(!rrect.isOval());
3202    SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY);
3203
3204    // RRect ops only handle simple, but not too simple, rrects.
3205    // Do any matrix crunching before we reset the draw state for device coords.
3206    const SkRect& rrectBounds = rrect.getBounds();
3207    SkRect bounds;
3208    viewMatrix.mapRect(&bounds, rrectBounds);
3209
3210    SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
3211    SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] +
3212                                                  viewMatrix[SkMatrix::kMSkewY]));
3213
3214    // Do mapping of stroke. Use -1 to indicate fill-only draws.
3215    SkScalar scaledStroke = -1;
3216    SkScalar strokeWidth = stroke.getWidth();
3217    SkStrokeRec::Style style = stroke.getStyle();
3218
3219    bool isStrokeOnly =
3220        SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
3221    bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
3222
3223    if (hasStroke) {
3224        if (SkStrokeRec::kHairline_Style == style) {
3225            scaledStroke = SK_Scalar1;
3226        } else {
3227            scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] +
3228                                                      viewMatrix[SkMatrix::kMSkewY]));
3229        }
3230    }
3231
3232    // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
3233    // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
3234    // patch will have fractional coverage. This only matters when the interior is actually filled.
3235    // We could consider falling back to rect rendering here, since a tiny radius is
3236    // indistinguishable from a square corner.
3237    if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) {
3238        return nullptr;
3239    }
3240
3241    return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius,
3242                                 scaledStroke, isStrokeOnly);
3243}
3244
3245GrOp::Owner make_rrect_op(GrRecordingContext* context,
3246                          GrPaint&& paint,
3247                          const SkMatrix& viewMatrix,
3248                          const SkRRect& rrect,
3249                          const SkStrokeRec& stroke) {
3250    SkASSERT(viewMatrix.rectStaysRect());
3251    SkASSERT(rrect.isSimple());
3252    SkASSERT(!rrect.isOval());
3253
3254    // RRect ops only handle simple, but not too simple, rrects.
3255    // Do any matrix crunching before we reset the draw state for device coords.
3256    const SkRect& rrectBounds = rrect.getBounds();
3257    SkRect bounds;
3258    viewMatrix.mapRect(&bounds, rrectBounds);
3259
3260    SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
3261    SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
3262                                   viewMatrix[SkMatrix::kMSkewY] * radii.fY);
3263    SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
3264                                   viewMatrix[SkMatrix::kMScaleY] * radii.fY);
3265
3266    SkStrokeRec::Style style = stroke.getStyle();
3267
3268    // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
3269    SkVector scaledStroke = {-1, -1};
3270    SkScalar strokeWidth = stroke.getWidth();
3271
3272    bool isStrokeOnly =
3273            SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
3274    bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
3275
3276    if (hasStroke) {
3277        if (SkStrokeRec::kHairline_Style == style) {
3278            scaledStroke.set(1, 1);
3279        } else {
3280            scaledStroke.fX = SkScalarAbs(
3281                    strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
3282            scaledStroke.fY = SkScalarAbs(
3283                    strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
3284        }
3285
3286        // if half of strokewidth is greater than radius, we don't handle that right now
3287        if ((SK_ScalarHalf * scaledStroke.fX > xRadius ||
3288             SK_ScalarHalf * scaledStroke.fY > yRadius)) {
3289            return nullptr;
3290        }
3291    }
3292
3293    // The matrix may have a rotation by an odd multiple of 90 degrees.
3294    if (viewMatrix.getScaleX() == 0) {
3295        std::swap(xRadius, yRadius);
3296        std::swap(scaledStroke.fX, scaledStroke.fY);
3297    }
3298
3299    // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
3300    // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
3301    // patch will have fractional coverage. This only matters when the interior is actually filled.
3302    // We could consider falling back to rect rendering here, since a tiny radius is
3303    // indistinguishable from a square corner.
3304    if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
3305        return nullptr;
3306    }
3307
3308    // if the corners are circles, use the circle renderer
3309    return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
3310                                   xRadius, yRadius, scaledStroke, isStrokeOnly);
3311}
3312
3313GrOp::Owner GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
3314                                         GrPaint&& paint,
3315                                         const SkMatrix& viewMatrix,
3316                                         const SkRRect& rrect,
3317                                         const SkStrokeRec& stroke,
3318                                         const GrShaderCaps* shaderCaps) {
3319    if (rrect.isOval()) {
3320        return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(),
3321                          GrStyle(stroke, nullptr), shaderCaps);
3322    }
3323
3324    if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
3325        return nullptr;
3326    }
3327
3328    return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke);
3329}
3330
3331///////////////////////////////////////////////////////////////////////////////
3332
3333GrOp::Owner GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context,
3334                                          GrPaint&& paint,
3335                                          const SkMatrix& viewMatrix,
3336                                          const SkRect& oval,
3337                                          const GrStyle& style,
3338                                          const GrShaderCaps* shaderCaps) {
3339    SkScalar width = oval.width();
3340    SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3341             circle_stays_circle(viewMatrix));
3342
3343    auto r = width / 2.f;
3344    SkPoint center = { oval.centerX(), oval.centerY() };
3345    if (style.hasNonDashPathEffect()) {
3346        return nullptr;
3347    } else if (style.isDashed()) {
3348        if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
3349            style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
3350            return nullptr;
3351        }
3352        auto onInterval = style.dashIntervals()[0];
3353        auto offInterval = style.dashIntervals()[1];
3354        if (offInterval == 0) {
3355            GrStyle strokeStyle(style.strokeRec(), nullptr);
3356            return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
3357                              strokeStyle, shaderCaps);
3358        } else if (onInterval == 0) {
3359            // There is nothing to draw but we have no way to indicate that here.
3360            return nullptr;
3361        }
3362        auto angularOnInterval = onInterval / r;
3363        auto angularOffInterval = offInterval / r;
3364        auto phaseAngle = style.dashPhase() / r;
3365        // Currently this function doesn't accept ovals with different start angles, though
3366        // it could.
3367        static const SkScalar kStartAngle = 0.f;
3368        return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
3369                                           style.strokeRec().getWidth(), kStartAngle,
3370                                           angularOnInterval, angularOffInterval, phaseAngle);
3371    }
3372    return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
3373}
3374
3375GrOp::Owner GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
3376                                        GrPaint&& paint,
3377                                        const SkMatrix& viewMatrix,
3378                                        const SkRect& oval,
3379                                        const GrStyle& style,
3380                                        const GrShaderCaps* shaderCaps) {
3381    if (style.pathEffect()) {
3382        return nullptr;
3383    }
3384
3385    // prefer the device space ellipse op for batchability
3386    if (viewMatrix.rectStaysRect()) {
3387        return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
3388    }
3389
3390    // Otherwise, if we have shader derivative support, render as device-independent
3391    if (shaderCaps->shaderDerivativeSupport()) {
3392        SkScalar a = viewMatrix[SkMatrix::kMScaleX];
3393        SkScalar b = viewMatrix[SkMatrix::kMSkewX];
3394        SkScalar c = viewMatrix[SkMatrix::kMSkewY];
3395        SkScalar d = viewMatrix[SkMatrix::kMScaleY];
3396        // Check for near-degenerate matrix
3397        if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) {
3398            return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval,
3399                                     style.strokeRec());
3400        }
3401    }
3402
3403    return nullptr;
3404}
3405
3406///////////////////////////////////////////////////////////////////////////////
3407
3408GrOp::Owner GrOvalOpFactory::MakeArcOp(GrRecordingContext* context,
3409                                       GrPaint&& paint,
3410                                       const SkMatrix& viewMatrix,
3411                                       const SkRect& oval, SkScalar startAngle,
3412                                       SkScalar sweepAngle, bool useCenter,
3413                                       const GrStyle& style,
3414                                       const GrShaderCaps* shaderCaps) {
3415    SkASSERT(!oval.isEmpty());
3416    SkASSERT(sweepAngle);
3417    SkScalar width = oval.width();
3418    if (SkScalarAbs(sweepAngle) >= 360.f) {
3419        return nullptr;
3420    }
3421    if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) {
3422        return nullptr;
3423    }
3424    SkPoint center = {oval.centerX(), oval.centerY()};
3425    CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle),
3426                                     useCenter};
3427    return CircleOp::Make(context, std::move(paint), viewMatrix,
3428                          center, width / 2.f, style, &arcParams);
3429}
3430
3431///////////////////////////////////////////////////////////////////////////////
3432
3433#if GR_TEST_UTILS
3434
3435GR_DRAW_OP_TEST_DEFINE(CircleOp) {
3436    if (numSamples > 1) {
3437        return nullptr;
3438    }
3439
3440    do {
3441        SkScalar rotate = random->nextSScalar1() * 360.f;
3442        SkScalar translateX = random->nextSScalar1() * 1000.f;
3443        SkScalar translateY = random->nextSScalar1() * 1000.f;
3444        SkScalar scale;
3445        do {
3446            scale = random->nextSScalar1() * 100.f;
3447        } while (scale == 0);
3448        SkMatrix viewMatrix;
3449        viewMatrix.setRotate(rotate);
3450        viewMatrix.postTranslate(translateX, translateY);
3451        viewMatrix.postScale(scale, scale);
3452        SkRect circle = GrTest::TestSquare(random);
3453        SkPoint center = {circle.centerX(), circle.centerY()};
3454        SkScalar radius = circle.width() / 2.f;
3455        SkStrokeRec stroke = GrTest::TestStrokeRec(random);
3456        CircleOp::ArcParams arcParamsTmp;
3457        const CircleOp::ArcParams* arcParams = nullptr;
3458        if (random->nextBool()) {
3459            arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2;
3460            arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f;
3461            arcParamsTmp.fUseCenter = random->nextBool();
3462            arcParams = &arcParamsTmp;
3463        }
3464        GrOp::Owner op = CircleOp::Make(context, std::move(paint), viewMatrix,
3465                                        center, radius,
3466                                        GrStyle(stroke, nullptr), arcParams);
3467        if (op) {
3468            return op;
3469        }
3470        assert_alive(paint);
3471    } while (true);
3472}
3473
3474GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) {
3475    if (numSamples > 1) {
3476        return nullptr;
3477    }
3478
3479    SkScalar rotate = random->nextSScalar1() * 360.f;
3480    SkScalar translateX = random->nextSScalar1() * 1000.f;
3481    SkScalar translateY = random->nextSScalar1() * 1000.f;
3482    SkScalar scale;
3483    do {
3484        scale = random->nextSScalar1() * 100.f;
3485    } while (scale == 0);
3486    SkMatrix viewMatrix;
3487    viewMatrix.setRotate(rotate);
3488    viewMatrix.postTranslate(translateX, translateY);
3489    viewMatrix.postScale(scale, scale);
3490    SkRect circle = GrTest::TestSquare(random);
3491    SkPoint center = {circle.centerX(), circle.centerY()};
3492    SkScalar radius = circle.width() / 2.f;
3493    SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius);
3494    SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f);
3495    SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f);
3496    SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f);
3497    SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f);
3498    return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix,
3499                                       center, radius, strokeWidth,
3500                                       startAngle, onAngle, offAngle, phase);
3501}
3502
3503GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
3504    SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
3505    SkRect ellipse = GrTest::TestSquare(random);
3506    return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
3507                           GrTest::TestStrokeRec(random));
3508}
3509
3510GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
3511    SkMatrix viewMatrix = GrTest::TestMatrix(random);
3512    SkRect ellipse = GrTest::TestSquare(random);
3513    return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
3514                             GrTest::TestStrokeRec(random));
3515}
3516
3517GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) {
3518    do {
3519        SkScalar rotate = random->nextSScalar1() * 360.f;
3520        SkScalar translateX = random->nextSScalar1() * 1000.f;
3521        SkScalar translateY = random->nextSScalar1() * 1000.f;
3522        SkScalar scale;
3523        do {
3524            scale = random->nextSScalar1() * 100.f;
3525        } while (scale == 0);
3526        SkMatrix viewMatrix;
3527        viewMatrix.setRotate(rotate);
3528        viewMatrix.postTranslate(translateX, translateY);
3529        viewMatrix.postScale(scale, scale);
3530        SkRect rect = GrTest::TestRect(random);
3531        SkScalar radius = random->nextRangeF(0.1f, 10.f);
3532        SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius);
3533        if (rrect.isOval()) {
3534            continue;
3535        }
3536        GrOp::Owner op =
3537                GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect,
3538                                                     GrTest::TestStrokeRec(random), nullptr);
3539        if (op) {
3540            return op;
3541        }
3542        assert_alive(paint);
3543    } while (true);
3544}
3545
3546GR_DRAW_OP_TEST_DEFINE(RRectOp) {
3547    SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
3548    const SkRRect& rrect = GrTest::TestRRectSimple(random);
3549    return make_rrect_op(context, std::move(paint), viewMatrix, rrect,
3550                         GrTest::TestStrokeRec(random));
3551}
3552
3553#endif
3554