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