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