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