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