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