Coverage Report

Created: 2021-08-22 09:07

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