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