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