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