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