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