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