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