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