blob: f9d004f10ad6ffcb6e0571dbcfb75aeb26e63580 [file] [log] [blame]
commit-bot@chromium.org81312832013-03-22 18:34:09 +00001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkStrokeRec.h"
9#include "src/core/SkRRectPriv.h"
10#include "src/gpu/GrCaps.h"
11#include "src/gpu/GrDrawOpTest.h"
12#include "src/gpu/GrGeometryProcessor.h"
13#include "src/gpu/GrOpFlushState.h"
14#include "src/gpu/GrProcessor.h"
15#include "src/gpu/GrResourceProvider.h"
16#include "src/gpu/GrShaderCaps.h"
17#include "src/gpu/GrStyle.h"
18#include "src/gpu/GrVertexWriter.h"
19#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
20#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
21#include "src/gpu/glsl/GrGLSLProgramDataManager.h"
22#include "src/gpu/glsl/GrGLSLUniformHandler.h"
23#include "src/gpu/glsl/GrGLSLUtil.h"
24#include "src/gpu/glsl/GrGLSLVarying.h"
25#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
26#include "src/gpu/ops/GrMeshDrawOp.h"
27#include "src/gpu/ops/GrOvalOpFactory.h"
28#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
joshualitt76e7fb62015-02-11 08:52:27 -080029
Ben Wagnerf08d1d02018-06-18 15:11:00 -040030#include <utility>
31
commit-bot@chromium.org81312832013-03-22 18:34:09 +000032namespace {
brianosmanbb2ff942016-02-11 14:15:18 -080033
Brian Salomon289e3d82016-12-14 15:52:56 -050034static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); }
Brian Osmane3caf2d2018-11-21 13:48:36 -050035
Brian Osman2b6e3902018-11-21 15:29:43 -050036// Produces TriStrip vertex data for an origin-centered rectangle from [-x, -y] to [x, y]
37static inline GrVertexWriter::TriStrip<float> origin_centered_tri_strip(float x, float y) {
38 return GrVertexWriter::TriStrip<float>{ -x, -y, x, y };
39};
40
commit-bot@chromium.org81312832013-03-22 18:34:09 +000041}
42
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000043///////////////////////////////////////////////////////////////////////////////
44
45/**
bsalomonce1c8862014-12-15 07:11:22 -080046 * The output of this effect is a modulation of the input color and coverage for a circle. It
47 * operates in a space normalized by the circle radius (outer radius in the case of a stroke)
bsalomoncdaa97b2016-03-08 08:30:14 -080048 * with origin at the circle center. Three vertex attributes are used:
bsalomonce1c8862014-12-15 07:11:22 -080049 * vec2f : position in device space of the bounding geometry vertices
bsalomoncdaa97b2016-03-08 08:30:14 -080050 * vec4ub: color
bsalomonce1c8862014-12-15 07:11:22 -080051 * vec4f : (p.xy, outerRad, innerRad)
52 * p is the position in the normalized space.
53 * outerRad is the outerRadius in device space.
54 * innerRad is the innerRadius in normalized space (ignored if not stroking).
bsalomon4f3a0ca2016-08-22 13:14:26 -070055 * Additional clip planes are supported for rendering circular arcs. The additional planes are
56 * either intersected or unioned together. Up to three planes are supported (an initial plane,
57 * a plane intersected with the initial plane, and a plane unioned with the first two). Only two
Brian Salomon53e4c3c2016-12-21 11:38:53 -050058 * are useful for any given arc, but having all three in one instance allows combining different
bsalomon4f3a0ca2016-08-22 13:14:26 -070059 * types of arcs.
Brian Salomon45c92202018-04-10 10:53:58 -040060 * Round caps for stroking are allowed as well. The caps are specified as two circle center points
61 * in the same space as p.xy.
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000062 */
63
bsalomoncdaa97b2016-03-08 08:30:14 -080064class CircleGeometryProcessor : public GrGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000065public:
Greg Daniel2655ede2019-04-10 00:49:28 +000066 CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane,
Brian Osmane3caf2d2018-11-21 13:48:36 -050067 bool roundCaps, bool wideColor, const SkMatrix& localMatrix)
Ethan Nicholasabff9562017-10-09 10:54:08 -040068 : INHERITED(kCircleGeometryProcessor_ClassID)
Brian Salomon45c92202018-04-10 10:53:58 -040069 , fLocalMatrix(localMatrix)
70 , fStroke(stroke) {
Brian Osmanf04fb3c2018-11-12 15:34:00 -050071 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
Brian Osmanc906d252018-12-04 11:17:46 -050072 fInColor = MakeColorAttribute("inColor", wideColor);
Brian Osmanf04fb3c2018-11-12 15:34:00 -050073 fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
74
bsalomon4f3a0ca2016-08-22 13:14:26 -070075 if (clipPlane) {
Brian Osmand4c29702018-09-14 16:16:55 -040076 fInClipPlane = {"inClipPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
bsalomon4f3a0ca2016-08-22 13:14:26 -070077 }
78 if (isectPlane) {
Brian Osmand4c29702018-09-14 16:16:55 -040079 fInIsectPlane = {"inIsectPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
bsalomon4f3a0ca2016-08-22 13:14:26 -070080 }
81 if (unionPlane) {
Brian Osmand4c29702018-09-14 16:16:55 -040082 fInUnionPlane = {"inUnionPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
bsalomon4f3a0ca2016-08-22 13:14:26 -070083 }
Brian Salomon45c92202018-04-10 10:53:58 -040084 if (roundCaps) {
85 SkASSERT(stroke);
86 SkASSERT(clipPlane);
Brian Osmand4c29702018-09-14 16:16:55 -040087 fInRoundCapCenters =
88 {"inRoundCapCenters", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
Brian Salomon45c92202018-04-10 10:53:58 -040089 }
Brian Osmanf04fb3c2018-11-12 15:34:00 -050090 this->setVertexAttributes(&fInPosition, 7);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000091 }
92
Brian Salomond3b65972017-03-22 12:05:03 -040093 ~CircleGeometryProcessor() override {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000094
mtklein36352bf2015-03-25 18:17:31 -070095 const char* name() const override { return "CircleEdge"; }
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000096
Brian Salomon94efbf52016-11-29 13:43:05 -050097 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
bsalomon31df31c2016-08-17 09:00:24 -070098 GLSLProcessor::GenKey(*this, caps, b);
99 }
100
Brian Salomon94efbf52016-11-29 13:43:05 -0500101 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700102 return new GLSLProcessor();
103 }
104
105private:
egdaniel57d3b032015-11-13 11:57:27 -0800106 class GLSLProcessor : public GrGLSLGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000107 public:
brianosmanbb2ff942016-02-11 14:15:18 -0800108 GLSLProcessor() {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000109
Brian Salomon289e3d82016-12-14 15:52:56 -0500110 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800111 const CircleGeometryProcessor& cgp = args.fGP.cast<CircleGeometryProcessor>();
egdaniel4ca2e602015-11-18 08:01:26 -0800112 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
egdaniel0eafe792015-11-20 14:01:22 -0800113 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
egdaniel7ea439b2015-12-03 09:20:44 -0800114 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
Chris Dalton60283612018-02-14 13:38:14 -0700115 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
joshualitt2dd1ae02014-12-03 06:24:10 -0800116
joshualittabb52a12015-01-13 15:02:10 -0800117 // emit attributes
bsalomoncdaa97b2016-03-08 08:30:14 -0800118 varyingHandler->emitAttributes(cgp);
Ethan Nicholas8aa45692017-09-20 11:24:15 -0400119 fragBuilder->codeAppend("float4 circleEdge;");
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500120 varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge, "circleEdge");
Brian Salomon92be2f72018-06-19 14:33:47 -0400121 if (cgp.fInClipPlane.isInitialized()) {
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400122 fragBuilder->codeAppend("half3 clipPlane;");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700123 varyingHandler->addPassThroughAttribute(cgp.fInClipPlane, "clipPlane");
124 }
Brian Salomon92be2f72018-06-19 14:33:47 -0400125 if (cgp.fInIsectPlane.isInitialized()) {
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400126 fragBuilder->codeAppend("half3 isectPlane;");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700127 varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane, "isectPlane");
128 }
Brian Salomon92be2f72018-06-19 14:33:47 -0400129 if (cgp.fInUnionPlane.isInitialized()) {
130 SkASSERT(cgp.fInClipPlane.isInitialized());
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400131 fragBuilder->codeAppend("half3 unionPlane;");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700132 varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane, "unionPlane");
133 }
Brian Salomon45c92202018-04-10 10:53:58 -0400134 GrGLSLVarying capRadius(kFloat_GrSLType);
Brian Salomon92be2f72018-06-19 14:33:47 -0400135 if (cgp.fInRoundCapCenters.isInitialized()) {
Brian Salomon45c92202018-04-10 10:53:58 -0400136 fragBuilder->codeAppend("float4 roundCapCenters;");
137 varyingHandler->addPassThroughAttribute(cgp.fInRoundCapCenters, "roundCapCenters");
138 varyingHandler->addVarying("capRadius", &capRadius,
139 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
140 // This is the cap radius in normalized space where the outer radius is 1 and
141 // circledEdge.w is the normalized inner radius.
142 vertBuilder->codeAppendf("%s = (1.0 - %s.w) / 2.0;", capRadius.vsOut(),
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500143 cgp.fInCircleEdge.name());
Brian Salomon45c92202018-04-10 10:53:58 -0400144 }
joshualittabb52a12015-01-13 15:02:10 -0800145
joshualittb8c241a2015-05-19 08:23:30 -0700146 // setup pass through color
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500147 varyingHandler->addPassThroughAttribute(cgp.fInColor, args.fOutputColor);
joshualitt9b989322014-12-15 14:16:27 -0800148
joshualittabb52a12015-01-13 15:02:10 -0800149 // Setup position
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500150 this->writeOutputPosition(vertBuilder, gpArgs, cgp.fInPosition.name());
joshualittabb52a12015-01-13 15:02:10 -0800151
152 // emit transforms
egdaniel7ea439b2015-12-03 09:20:44 -0800153 this->emitTransforms(vertBuilder,
egdaniel0eafe792015-11-20 14:01:22 -0800154 varyingHandler,
egdaniel7ea439b2015-12-03 09:20:44 -0800155 uniformHandler,
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500156 cgp.fInPosition.asShaderVar(),
bsalomon31df31c2016-08-17 09:00:24 -0700157 cgp.fLocalMatrix,
bsalomona624bf32016-09-20 09:12:47 -0700158 args.fFPCoordTransformHandler);
joshualitt4973d9d2014-11-08 09:24:25 -0800159
Ethan Nicholas8aa45692017-09-20 11:24:15 -0400160 fragBuilder->codeAppend("float d = length(circleEdge.xy);");
Ethan Nicholase1f55022019-02-05 17:17:40 -0500161 fragBuilder->codeAppend("half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));");
Greg Daniel2655ede2019-04-10 00:49:28 +0000162 fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);");
bsalomoncdaa97b2016-03-08 08:30:14 -0800163 if (cgp.fStroke) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500164 fragBuilder->codeAppend(
Ethan Nicholase1f55022019-02-05 17:17:40 -0500165 "half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w));");
Greg Daniel2655ede2019-04-10 00:49:28 +0000166 fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);");
egdaniel4ca2e602015-11-18 08:01:26 -0800167 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000168 }
commit-bot@chromium.org0a6cb602013-04-11 15:05:37 +0000169
Brian Salomon92be2f72018-06-19 14:33:47 -0400170 if (cgp.fInClipPlane.isInitialized()) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500171 fragBuilder->codeAppend(
Greg Daniel2655ede2019-04-10 00:49:28 +0000172 "half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, "
173 "clipPlane.xy) + clipPlane.z));");
Brian Salomon92be2f72018-06-19 14:33:47 -0400174 if (cgp.fInIsectPlane.isInitialized()) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500175 fragBuilder->codeAppend(
Greg Daniel2655ede2019-04-10 00:49:28 +0000176 "clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, "
177 "isectPlane.xy) + isectPlane.z));");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700178 }
Brian Salomon92be2f72018-06-19 14:33:47 -0400179 if (cgp.fInUnionPlane.isInitialized()) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500180 fragBuilder->codeAppend(
Greg Daniel2655ede2019-04-10 00:49:28 +0000181 "clip = saturate(clip + half(saturate(circleEdge.z * dot(circleEdge.xy,"
182 " unionPlane.xy) + unionPlane.z)));");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700183 }
184 fragBuilder->codeAppend("edgeAlpha *= clip;");
Brian Salomon92be2f72018-06-19 14:33:47 -0400185 if (cgp.fInRoundCapCenters.isInitialized()) {
Brian Salomon33c44222018-04-19 08:44:21 -0400186 // We compute coverage of the round caps as circles at the butt caps produced
187 // by the clip planes. The inverse of the clip planes is applied so that there
188 // is no double counting.
Brian Salomon45c92202018-04-10 10:53:58 -0400189 fragBuilder->codeAppendf(
Ethan Nicholase1f55022019-02-05 17:17:40 -0500190 "half dcap1 = half(circleEdge.z * (%s - length(circleEdge.xy - "
191 " roundCapCenters.xy)));"
192 "half dcap2 = half(circleEdge.z * (%s - length(circleEdge.xy - "
193 " roundCapCenters.zw)));"
Brian Salomon33c44222018-04-19 08:44:21 -0400194 "half capAlpha = (1 - clip) * (max(dcap1, 0) + max(dcap2, 0));"
Brian Salomon45c92202018-04-10 10:53:58 -0400195 "edgeAlpha = min(edgeAlpha + capAlpha, 1.0);",
196 capRadius.fsIn(), capRadius.fsIn());
197 }
bsalomon4f3a0ca2016-08-22 13:14:26 -0700198 }
Brian Salomonea26d6b2018-01-23 20:33:21 +0000199 fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000200 }
201
robertphillips46d36f02015-01-18 08:14:14 -0800202 static void GenKey(const GrGeometryProcessor& gp,
Brian Salomon94efbf52016-11-29 13:43:05 -0500203 const GrShaderCaps&,
joshualittb0a8a372014-09-23 09:50:21 -0700204 GrProcessorKeyBuilder* b) {
bsalomoncdaa97b2016-03-08 08:30:14 -0800205 const CircleGeometryProcessor& cgp = gp.cast<CircleGeometryProcessor>();
bsalomon4f3a0ca2016-08-22 13:14:26 -0700206 uint16_t key;
Brian Salomon289e3d82016-12-14 15:52:56 -0500207 key = cgp.fStroke ? 0x01 : 0x0;
bsalomon4f3a0ca2016-08-22 13:14:26 -0700208 key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0;
Brian Salomon92be2f72018-06-19 14:33:47 -0400209 key |= cgp.fInClipPlane.isInitialized() ? 0x04 : 0x0;
210 key |= cgp.fInIsectPlane.isInitialized() ? 0x08 : 0x0;
211 key |= cgp.fInUnionPlane.isInitialized() ? 0x10 : 0x0;
212 key |= cgp.fInRoundCapCenters.isInitialized() ? 0x20 : 0x0;
joshualittb8c241a2015-05-19 08:23:30 -0700213 b->add32(key);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000214 }
215
bsalomona624bf32016-09-20 09:12:47 -0700216 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
217 FPCoordTransformIter&& transformIter) override {
bsalomone4f24612016-08-17 10:30:17 -0700218 this->setTransformDataHelper(primProc.cast<CircleGeometryProcessor>().fLocalMatrix,
bsalomona624bf32016-09-20 09:12:47 -0700219 pdman, &transformIter);
joshualitte3ababe2015-05-15 07:56:07 -0700220 }
221
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000222 private:
egdaniele659a582015-11-13 09:55:43 -0800223 typedef GrGLSLGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000224 };
225
Brian Salomon289e3d82016-12-14 15:52:56 -0500226 SkMatrix fLocalMatrix;
Brian Salomon92be2f72018-06-19 14:33:47 -0400227
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500228 Attribute fInPosition;
229 Attribute fInColor;
230 Attribute fInCircleEdge;
Brian Salomon92be2f72018-06-19 14:33:47 -0400231 // Optional attributes.
232 Attribute fInClipPlane;
233 Attribute fInIsectPlane;
234 Attribute fInUnionPlane;
235 Attribute fInRoundCapCenters;
236
Brian Salomon289e3d82016-12-14 15:52:56 -0500237 bool fStroke;
Brian Salomon0c26a9d2017-07-06 10:09:38 -0400238 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000239
joshualitt249af152014-09-15 11:41:13 -0700240 typedef GrGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000241};
242
bsalomoncdaa97b2016-03-08 08:30:14 -0800243GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000244
Hal Canary6f6961e2017-01-31 13:50:44 -0500245#if GR_TEST_UTILS
bungeman06ca8ec2016-06-09 08:01:03 -0700246sk_sp<GrGeometryProcessor> CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Salomon45c92202018-04-10 10:53:58 -0400247 bool stroke = d->fRandom->nextBool();
248 bool roundCaps = stroke ? d->fRandom->nextBool() : false;
Brian Osmane3caf2d2018-11-21 13:48:36 -0500249 bool wideColor = d->fRandom->nextBool();
Brian Salomon45c92202018-04-10 10:53:58 -0400250 bool clipPlane = d->fRandom->nextBool();
251 bool isectPlane = d->fRandom->nextBool();
252 bool unionPlane = d->fRandom->nextBool();
253 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
Brian Osmane3caf2d2018-11-21 13:48:36 -0500254 return sk_sp<GrGeometryProcessor>(new CircleGeometryProcessor(
Greg Daniel2655ede2019-04-10 00:49:28 +0000255 stroke, clipPlane, isectPlane, unionPlane, roundCaps, wideColor, matrix));
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000256}
Hal Canary6f6961e2017-01-31 13:50:44 -0500257#endif
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000258
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400259class ButtCapDashedCircleGeometryProcessor : public GrGeometryProcessor {
260public:
Brian Osmane3caf2d2018-11-21 13:48:36 -0500261 ButtCapDashedCircleGeometryProcessor(bool wideColor, const SkMatrix& localMatrix)
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400262 : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID), fLocalMatrix(localMatrix) {
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500263 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
Brian Osmanc906d252018-12-04 11:17:46 -0500264 fInColor = MakeColorAttribute("inColor", wideColor);
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500265 fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
266 fInDashParams = {"inDashParams", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
267 this->setVertexAttributes(&fInPosition, 4);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400268 }
269
270 ~ButtCapDashedCircleGeometryProcessor() override {}
271
272 const char* name() const override { return "ButtCapDashedCircleGeometryProcessor"; }
273
274 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
275 GLSLProcessor::GenKey(*this, caps, b);
276 }
277
278 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
279 return new GLSLProcessor();
280 }
281
282private:
283 class GLSLProcessor : public GrGLSLGeometryProcessor {
284 public:
285 GLSLProcessor() {}
286
287 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
288 const ButtCapDashedCircleGeometryProcessor& bcscgp =
289 args.fGP.cast<ButtCapDashedCircleGeometryProcessor>();
290 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
291 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
292 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
293 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
294
295 // emit attributes
296 varyingHandler->emitAttributes(bcscgp);
297 fragBuilder->codeAppend("float4 circleEdge;");
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500298 varyingHandler->addPassThroughAttribute(bcscgp.fInCircleEdge, "circleEdge");
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400299
300 fragBuilder->codeAppend("float4 dashParams;");
301 varyingHandler->addPassThroughAttribute(
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500302 bcscgp.fInDashParams, "dashParams",
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400303 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
304 GrGLSLVarying wrapDashes(kHalf4_GrSLType);
305 varyingHandler->addVarying("wrapDashes", &wrapDashes,
306 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
307 GrGLSLVarying lastIntervalLength(kHalf_GrSLType);
308 varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength,
309 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500310 vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.fInDashParams.name());
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400311 // Our fragment shader works in on/off intervals as specified by dashParams.xy:
312 // x = length of on interval, y = length of on + off.
313 // There are two other parameters in dashParams.zw:
314 // z = start angle in radians, w = phase offset in radians in range -y/2..y/2.
315 // Each interval has a "corresponding" dash which may be shifted partially or
316 // fully out of its interval by the phase. So there may be up to two "visual"
317 // dashes in an interval.
318 // When computing coverage in an interval we look at three dashes. These are the
319 // "corresponding" dashes from the current, previous, and next intervals. Any of these
320 // may be phase shifted into our interval or even when phase=0 they may be within half a
321 // pixel distance of a pixel center in the interval.
322 // When in the first interval we need to check the dash from the last interval. And
323 // similarly when in the last interval we need to check the dash from the first
324 // interval. When 2pi is not perfectly divisible dashParams.y this is a boundary case.
325 // We compute the dash begin/end angles in the vertex shader and apply them in the
326 // fragment shader when we detect we're in the first/last interval.
327 vertBuilder->codeAppend(R"(
328 // The two boundary dash intervals are stored in wrapDashes.xy and .zw and fed
329 // to the fragment shader as a varying.
330 float4 wrapDashes;
Ethan Nicholase1f55022019-02-05 17:17:40 -0500331 half lastIntervalLength = mod(6.28318530718, half(dashParams.y));
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400332 // We can happen to be perfectly divisible.
333 if (0 == lastIntervalLength) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500334 lastIntervalLength = half(dashParams.y);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400335 }
336 // Let 'l' be the last interval before reaching 2 pi.
337 // Based on the phase determine whether (l-1)th, l-th, or (l+1)th interval's
338 // "corresponding" dash appears in the l-th interval and is closest to the 0-th
339 // interval.
340 half offset = 0;
341 if (-dashParams.w >= lastIntervalLength) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500342 offset = half(-dashParams.y);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400343 } else if (dashParams.w > dashParams.y - lastIntervalLength) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500344 offset = half(dashParams.y);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400345 }
346 wrapDashes.x = -lastIntervalLength + offset - dashParams.w;
347 // The end of this dash may be beyond the 2 pi and therefore clipped. Hence the
348 // min.
349 wrapDashes.y = min(wrapDashes.x + dashParams.x, 0);
350
351 // Based on the phase determine whether the -1st, 0th, or 1st interval's
352 // "corresponding" dash appears in the 0th interval and is closest to l.
353 offset = 0;
354 if (dashParams.w >= dashParams.x) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500355 offset = half(dashParams.y);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400356 } else if (-dashParams.w > dashParams.y - dashParams.x) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500357 offset = half(-dashParams.y);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400358 }
359 wrapDashes.z = lastIntervalLength + offset - dashParams.w;
360 wrapDashes.w = wrapDashes.z + dashParams.x;
361 // The start of the dash we're considering may be clipped by the start of the
362 // circle.
363 wrapDashes.z = max(wrapDashes.z, lastIntervalLength);
364 )");
Ethan Nicholase1f55022019-02-05 17:17:40 -0500365 vertBuilder->codeAppendf("%s = half4(wrapDashes);", wrapDashes.vsOut());
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400366 vertBuilder->codeAppendf("%s = lastIntervalLength;", lastIntervalLength.vsOut());
367 fragBuilder->codeAppendf("half4 wrapDashes = %s;", wrapDashes.fsIn());
368 fragBuilder->codeAppendf("half lastIntervalLength = %s;", lastIntervalLength.fsIn());
369
370 // setup pass through color
371 varyingHandler->addPassThroughAttribute(
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500372 bcscgp.fInColor, args.fOutputColor,
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400373 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
374
375 // Setup position
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500376 this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition.name());
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400377
378 // emit transforms
379 this->emitTransforms(vertBuilder,
380 varyingHandler,
381 uniformHandler,
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500382 bcscgp.fInPosition.asShaderVar(),
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400383 bcscgp.fLocalMatrix,
384 args.fFPCoordTransformHandler);
385 GrShaderVar fnArgs[] = {
386 GrShaderVar("angleToEdge", kFloat_GrSLType),
387 GrShaderVar("diameter", kFloat_GrSLType),
388 };
389 SkString fnName;
390 fragBuilder->emitFunction(kFloat_GrSLType, "coverage_from_dash_edge",
391 SK_ARRAY_COUNT(fnArgs), fnArgs, R"(
392 float linearDist;
393 angleToEdge = clamp(angleToEdge, -3.1415, 3.1415);
394 linearDist = diameter * sin(angleToEdge / 2);
Ethan Nicholas12fb9cf2018-08-03 16:16:57 -0400395 return saturate(linearDist + 0.5);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400396 )",
397 &fnName);
398 fragBuilder->codeAppend(R"(
399 float d = length(circleEdge.xy) * circleEdge.z;
400
401 // Compute coverage from outer/inner edges of the stroke.
Ethan Nicholase1f55022019-02-05 17:17:40 -0500402 half distanceToOuterEdge = half(circleEdge.z - d);
Ethan Nicholas12fb9cf2018-08-03 16:16:57 -0400403 half edgeAlpha = saturate(distanceToOuterEdge);
Ethan Nicholase1f55022019-02-05 17:17:40 -0500404 half distanceToInnerEdge = half(d - circleEdge.z * circleEdge.w);
Ethan Nicholas12fb9cf2018-08-03 16:16:57 -0400405 half innerAlpha = saturate(distanceToInnerEdge);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400406 edgeAlpha *= innerAlpha;
407
Ethan Nicholase1f55022019-02-05 17:17:40 -0500408 half angleFromStart = half(atan(circleEdge.y, circleEdge.x) - dashParams.z);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400409 angleFromStart = mod(angleFromStart, 6.28318530718);
410 float x = mod(angleFromStart, dashParams.y);
411 // Convert the radial distance from center to pixel into a diameter.
412 d *= 2;
Ethan Nicholase1f55022019-02-05 17:17:40 -0500413 half2 currDash = half2(half(-dashParams.w), half(dashParams.x) -
414 half(dashParams.w));
415 half2 nextDash = half2(half(dashParams.y) - half(dashParams.w),
416 half(dashParams.y) + half(dashParams.x) -
417 half(dashParams.w));
418 half2 prevDash = half2(half(-dashParams.y) - half(dashParams.w),
419 half(-dashParams.y) + half(dashParams.x) -
420 half(dashParams.w));
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400421 half dashAlpha = 0;
422 )");
423 fragBuilder->codeAppendf(R"(
424 if (angleFromStart - x + dashParams.y >= 6.28318530718) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500425 dashAlpha += half(%s(x - wrapDashes.z, d) * %s(wrapDashes.w - x, d));
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400426 currDash.y = min(currDash.y, lastIntervalLength);
427 if (nextDash.x >= lastIntervalLength) {
428 // The next dash is outside the 0..2pi range, throw it away
429 nextDash.xy = half2(1000);
430 } else {
431 // Clip the end of the next dash to the end of the circle
432 nextDash.y = min(nextDash.y, lastIntervalLength);
433 }
434 }
435 )", fnName.c_str(), fnName.c_str());
436 fragBuilder->codeAppendf(R"(
437 if (angleFromStart - x - dashParams.y < -0.01) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500438 dashAlpha += half(%s(x - wrapDashes.x, d) * %s(wrapDashes.y - x, d));
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400439 currDash.x = max(currDash.x, 0);
440 if (prevDash.y <= 0) {
441 // The previous dash is outside the 0..2pi range, throw it away
442 prevDash.xy = half2(1000);
443 } else {
444 // Clip the start previous dash to the start of the circle
445 prevDash.x = max(prevDash.x, 0);
446 }
447 }
448 )", fnName.c_str(), fnName.c_str());
449 fragBuilder->codeAppendf(R"(
Ethan Nicholase1f55022019-02-05 17:17:40 -0500450 dashAlpha += half(%s(x - currDash.x, d) * %s(currDash.y - x, d));
451 dashAlpha += half(%s(x - nextDash.x, d) * %s(nextDash.y - x, d));
452 dashAlpha += half(%s(x - prevDash.x, d) * %s(prevDash.y - x, d));
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400453 dashAlpha = min(dashAlpha, 1);
454 edgeAlpha *= dashAlpha;
455 )", fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(),
456 fnName.c_str());
457 fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
458 }
459
460 static void GenKey(const GrGeometryProcessor& gp,
461 const GrShaderCaps&,
462 GrProcessorKeyBuilder* b) {
463 const ButtCapDashedCircleGeometryProcessor& bcscgp =
464 gp.cast<ButtCapDashedCircleGeometryProcessor>();
465 b->add32(bcscgp.fLocalMatrix.hasPerspective());
466 }
467
468 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
469 FPCoordTransformIter&& transformIter) override {
470 this->setTransformDataHelper(
471 primProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix, pdman,
472 &transformIter);
473 }
474
475 private:
476 typedef GrGLSLGeometryProcessor INHERITED;
477 };
478
479 SkMatrix fLocalMatrix;
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500480 Attribute fInPosition;
481 Attribute fInColor;
482 Attribute fInCircleEdge;
483 Attribute fInDashParams;
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400484
485 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
486
487 typedef GrGeometryProcessor INHERITED;
488};
489
490#if GR_TEST_UTILS
491sk_sp<GrGeometryProcessor> ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Osmane3caf2d2018-11-21 13:48:36 -0500492 bool wideColor = d->fRandom->nextBool();
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400493 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
Brian Osmane3caf2d2018-11-21 13:48:36 -0500494 return sk_sp<GrGeometryProcessor>(new ButtCapDashedCircleGeometryProcessor(wideColor, matrix));
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400495}
496#endif
497
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000498///////////////////////////////////////////////////////////////////////////////
499
500/**
501 * The output of this effect is a modulation of the input color and coverage for an axis-aligned
skia.committer@gmail.com8be02fc2013-05-17 07:01:11 +0000502 * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii,
503 * in both x and y directions.
504 *
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +0000505 * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0.
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000506 */
507
bsalomoncdaa97b2016-03-08 08:30:14 -0800508class EllipseGeometryProcessor : public GrGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000509public:
Greg Daniel2655ede2019-04-10 00:49:28 +0000510 EllipseGeometryProcessor(bool stroke, bool wideColor, bool useScale,
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400511 const SkMatrix& localMatrix)
Brian Salomonea26d6b2018-01-23 20:33:21 +0000512 : INHERITED(kEllipseGeometryProcessor_ClassID)
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400513 , fLocalMatrix(localMatrix)
514 , fStroke(stroke)
515 , fUseScale(useScale) {
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500516 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
Brian Osmanc906d252018-12-04 11:17:46 -0500517 fInColor = MakeColorAttribute("inColor", wideColor);
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400518 if (useScale) {
519 fInEllipseOffset = {"inEllipseOffset", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
520 } else {
521 fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
522 }
523 fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500524 this->setVertexAttributes(&fInPosition, 4);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000525 }
526
Brian Salomond3b65972017-03-22 12:05:03 -0400527 ~EllipseGeometryProcessor() override {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000528
mtklein36352bf2015-03-25 18:17:31 -0700529 const char* name() const override { return "EllipseEdge"; }
joshualitt2dd1ae02014-12-03 06:24:10 -0800530
Brian Salomon94efbf52016-11-29 13:43:05 -0500531 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700532 GLSLProcessor::GenKey(*this, caps, b);
533 }
534
Brian Salomon94efbf52016-11-29 13:43:05 -0500535 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700536 return new GLSLProcessor();
537 }
538
539private:
egdaniel57d3b032015-11-13 11:57:27 -0800540 class GLSLProcessor : public GrGLSLGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000541 public:
brianosmanbb2ff942016-02-11 14:15:18 -0800542 GLSLProcessor() {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000543
Brian Salomon289e3d82016-12-14 15:52:56 -0500544 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800545 const EllipseGeometryProcessor& egp = args.fGP.cast<EllipseGeometryProcessor>();
egdaniel4ca2e602015-11-18 08:01:26 -0800546 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
egdaniel0eafe792015-11-20 14:01:22 -0800547 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
egdaniel7ea439b2015-12-03 09:20:44 -0800548 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000549
joshualittabb52a12015-01-13 15:02:10 -0800550 // emit attributes
bsalomoncdaa97b2016-03-08 08:30:14 -0800551 varyingHandler->emitAttributes(egp);
joshualittabb52a12015-01-13 15:02:10 -0800552
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400553 GrSLType offsetType = egp.fUseScale ? kFloat3_GrSLType : kFloat2_GrSLType;
554 GrGLSLVarying ellipseOffsets(offsetType);
egdaniel0eafe792015-11-20 14:01:22 -0800555 varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets);
egdaniel4ca2e602015-11-18 08:01:26 -0800556 vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(),
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500557 egp.fInEllipseOffset.name());
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000558
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400559 GrGLSLVarying ellipseRadii(kFloat4_GrSLType);
egdaniel0eafe792015-11-20 14:01:22 -0800560 varyingHandler->addVarying("EllipseRadii", &ellipseRadii);
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500561 vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii.name());
joshualitt2dd1ae02014-12-03 06:24:10 -0800562
Chris Dalton60283612018-02-14 13:38:14 -0700563 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
joshualittb8c241a2015-05-19 08:23:30 -0700564 // setup pass through color
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500565 varyingHandler->addPassThroughAttribute(egp.fInColor, args.fOutputColor);
joshualitt9b989322014-12-15 14:16:27 -0800566
joshualittabb52a12015-01-13 15:02:10 -0800567 // Setup position
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500568 this->writeOutputPosition(vertBuilder, gpArgs, egp.fInPosition.name());
joshualittabb52a12015-01-13 15:02:10 -0800569
570 // emit transforms
egdaniel7ea439b2015-12-03 09:20:44 -0800571 this->emitTransforms(vertBuilder,
egdaniel0eafe792015-11-20 14:01:22 -0800572 varyingHandler,
egdaniel7ea439b2015-12-03 09:20:44 -0800573 uniformHandler,
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500574 egp.fInPosition.asShaderVar(),
bsalomon31df31c2016-08-17 09:00:24 -0700575 egp.fLocalMatrix,
bsalomona624bf32016-09-20 09:12:47 -0700576 args.fFPCoordTransformHandler);
Jim Van Verthb1b87d92018-06-28 10:46:05 -0400577 // For stroked ellipses, we use the full ellipse equation (x^2/a^2 + y^2/b^2 = 1)
578 // to compute both the edges because we need two separate test equations for
579 // the single offset.
580 // For filled ellipses we can use a unit circle equation (x^2 + y^2 = 1), and warp
581 // the distance by the gradient, non-uniformly scaled by the inverse of the
582 // ellipse size.
joshualitt4973d9d2014-11-08 09:24:25 -0800583
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400584 // On medium precision devices, we scale the denominator of the distance equation
585 // before taking the inverse square root to minimize the chance that we're dividing
586 // by zero, then we scale the result back.
587
skia.committer@gmail.com8be02fc2013-05-17 07:01:11 +0000588 // for outer curve
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400589 fragBuilder->codeAppendf("float2 offset = %s.xy;", ellipseOffsets.fsIn());
Jim Van Verthb1b87d92018-06-28 10:46:05 -0400590 if (egp.fStroke) {
591 fragBuilder->codeAppendf("offset *= %s.xy;", ellipseRadii.fsIn());
592 }
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400593 fragBuilder->codeAppend("float test = dot(offset, offset) - 1.0;");
594 if (egp.fUseScale) {
595 fragBuilder->codeAppendf("float2 grad = 2.0*offset*(%s.z*%s.xy);",
596 ellipseOffsets.fsIn(), ellipseRadii.fsIn());
597 } else {
598 fragBuilder->codeAppendf("float2 grad = 2.0*offset*%s.xy;", ellipseRadii.fsIn());
599 }
600 fragBuilder->codeAppend("float grad_dot = dot(grad, grad);");
joshualitt74077b92014-10-24 11:26:03 -0700601
commit-bot@chromium.org1b035d82014-04-09 17:11:09 +0000602 // avoid calling inversesqrt on zero.
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400603 if (args.fShaderCaps->floatIs32Bits()) {
604 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);");
605 } else {
606 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
607 }
608 if (egp.fUseScale) {
609 fragBuilder->codeAppendf("float invlen = %s.z*inversesqrt(grad_dot);",
610 ellipseOffsets.fsIn());
611 } else {
612 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
613 }
Greg Daniel2655ede2019-04-10 00:49:28 +0000614 fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000615
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +0000616 // for inner curve
bsalomoncdaa97b2016-03-08 08:30:14 -0800617 if (egp.fStroke) {
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400618 fragBuilder->codeAppendf("offset = %s.xy*%s.zw;", ellipseOffsets.fsIn(),
egdaniel4ca2e602015-11-18 08:01:26 -0800619 ellipseRadii.fsIn());
Jim Van Verthb1b87d92018-06-28 10:46:05 -0400620 fragBuilder->codeAppend("test = dot(offset, offset) - 1.0;");
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400621 if (egp.fUseScale) {
622 fragBuilder->codeAppendf("grad = 2.0*offset*(%s.z*%s.zw);",
623 ellipseOffsets.fsIn(), ellipseRadii.fsIn());
624 } else {
625 fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn());
626 }
627 fragBuilder->codeAppend("grad_dot = dot(grad, grad);");
628 if (!args.fShaderCaps->floatIs32Bits()) {
629 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
630 }
631 if (egp.fUseScale) {
632 fragBuilder->codeAppendf("invlen = %s.z*inversesqrt(grad_dot);",
633 ellipseOffsets.fsIn());
634 } else {
635 fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
636 }
Greg Daniel2655ede2019-04-10 00:49:28 +0000637 fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000638 }
639
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400640 fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000641 }
642
robertphillips46d36f02015-01-18 08:14:14 -0800643 static void GenKey(const GrGeometryProcessor& gp,
Brian Salomon94efbf52016-11-29 13:43:05 -0500644 const GrShaderCaps&,
joshualittb0a8a372014-09-23 09:50:21 -0700645 GrProcessorKeyBuilder* b) {
bsalomoncdaa97b2016-03-08 08:30:14 -0800646 const EllipseGeometryProcessor& egp = gp.cast<EllipseGeometryProcessor>();
647 uint16_t key = egp.fStroke ? 0x1 : 0x0;
bsalomon31df31c2016-08-17 09:00:24 -0700648 key |= egp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0;
joshualittb8c241a2015-05-19 08:23:30 -0700649 b->add32(key);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000650 }
651
bsalomona624bf32016-09-20 09:12:47 -0700652 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
653 FPCoordTransformIter&& transformIter) override {
654 const EllipseGeometryProcessor& egp = primProc.cast<EllipseGeometryProcessor>();
655 this->setTransformDataHelper(egp.fLocalMatrix, pdman, &transformIter);
joshualitte3ababe2015-05-15 07:56:07 -0700656 }
657
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000658 private:
egdaniele659a582015-11-13 09:55:43 -0800659 typedef GrGLSLGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000660 };
661
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500662 Attribute fInPosition;
663 Attribute fInColor;
664 Attribute fInEllipseOffset;
665 Attribute fInEllipseRadii;
Brian Salomon92be2f72018-06-19 14:33:47 -0400666
joshualitte3ababe2015-05-15 07:56:07 -0700667 SkMatrix fLocalMatrix;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000668 bool fStroke;
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400669 bool fUseScale;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000670
Brian Salomon0c26a9d2017-07-06 10:09:38 -0400671 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000672
joshualitt249af152014-09-15 11:41:13 -0700673 typedef GrGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000674};
675
bsalomoncdaa97b2016-03-08 08:30:14 -0800676GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000677
Hal Canary6f6961e2017-01-31 13:50:44 -0500678#if GR_TEST_UTILS
bungeman06ca8ec2016-06-09 08:01:03 -0700679sk_sp<GrGeometryProcessor> EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Salomonea26d6b2018-01-23 20:33:21 +0000680 return sk_sp<GrGeometryProcessor>(
Brian Osmane3caf2d2018-11-21 13:48:36 -0500681 new EllipseGeometryProcessor(d->fRandom->nextBool(), d->fRandom->nextBool(),
Greg Daniel2655ede2019-04-10 00:49:28 +0000682 d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom)));
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000683}
Hal Canary6f6961e2017-01-31 13:50:44 -0500684#endif
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000685
686///////////////////////////////////////////////////////////////////////////////
687
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000688/**
skia.committer@gmail.com6fc1b492013-09-06 07:01:45 +0000689 * The output of this effect is a modulation of the input color and coverage for an ellipse,
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000690 * specified as a 2D offset from center for both the outer and inner paths (if stroked). The
691 * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by
692 * using differentials.
693 *
694 * The result is device-independent and can be used with any affine matrix.
695 */
696
bsalomoncdaa97b2016-03-08 08:30:14 -0800697enum class DIEllipseStyle { kStroke = 0, kHairline, kFill };
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000698
bsalomoncdaa97b2016-03-08 08:30:14 -0800699class DIEllipseGeometryProcessor : public GrGeometryProcessor {
700public:
Greg Daniel2655ede2019-04-10 00:49:28 +0000701 DIEllipseGeometryProcessor(bool wideColor, bool useScale, const SkMatrix& viewMatrix,
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400702 DIEllipseStyle style)
Ethan Nicholasabff9562017-10-09 10:54:08 -0400703 : INHERITED(kDIEllipseGeometryProcessor_ClassID)
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400704 , fViewMatrix(viewMatrix)
705 , fUseScale(useScale)
706 , fStyle(style) {
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500707 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
Brian Osmanc906d252018-12-04 11:17:46 -0500708 fInColor = MakeColorAttribute("inColor", wideColor);
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400709 if (useScale) {
710 fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat3_GrVertexAttribType,
711 kFloat3_GrSLType};
712 } else {
713 fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType,
714 kFloat2_GrSLType};
715 }
716 fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500717 this->setVertexAttributes(&fInPosition, 4);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000718 }
719
Brian Salomond3b65972017-03-22 12:05:03 -0400720 ~DIEllipseGeometryProcessor() override {}
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000721
mtklein36352bf2015-03-25 18:17:31 -0700722 const char* name() const override { return "DIEllipseEdge"; }
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000723
Brian Salomon94efbf52016-11-29 13:43:05 -0500724 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700725 GLSLProcessor::GenKey(*this, caps, b);
726 }
halcanary9d524f22016-03-29 09:03:52 -0700727
Brian Salomon94efbf52016-11-29 13:43:05 -0500728 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700729 return new GLSLProcessor();
730 }
731
732private:
egdaniel57d3b032015-11-13 11:57:27 -0800733 class GLSLProcessor : public GrGLSLGeometryProcessor {
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000734 public:
Brian Salomon289e3d82016-12-14 15:52:56 -0500735 GLSLProcessor() : fViewMatrix(SkMatrix::InvalidMatrix()) {}
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000736
joshualitt465283c2015-09-11 08:19:35 -0700737 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800738 const DIEllipseGeometryProcessor& diegp = args.fGP.cast<DIEllipseGeometryProcessor>();
egdaniel4ca2e602015-11-18 08:01:26 -0800739 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
egdaniel0eafe792015-11-20 14:01:22 -0800740 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
egdaniel7ea439b2015-12-03 09:20:44 -0800741 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000742
joshualittabb52a12015-01-13 15:02:10 -0800743 // emit attributes
bsalomoncdaa97b2016-03-08 08:30:14 -0800744 varyingHandler->emitAttributes(diegp);
joshualittabb52a12015-01-13 15:02:10 -0800745
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400746 GrSLType offsetType = (diegp.fUseScale) ? kFloat3_GrSLType : kFloat2_GrSLType;
747 GrGLSLVarying offsets0(offsetType);
egdaniel0eafe792015-11-20 14:01:22 -0800748 varyingHandler->addVarying("EllipseOffsets0", &offsets0);
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500749 vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.fInEllipseOffsets0.name());
joshualitt74077b92014-10-24 11:26:03 -0700750
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400751 GrGLSLVarying offsets1(kFloat2_GrSLType);
egdaniel0eafe792015-11-20 14:01:22 -0800752 varyingHandler->addVarying("EllipseOffsets1", &offsets1);
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500753 vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1.name());
joshualitt2dd1ae02014-12-03 06:24:10 -0800754
Chris Dalton60283612018-02-14 13:38:14 -0700755 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500756 varyingHandler->addPassThroughAttribute(diegp.fInColor, args.fOutputColor);
joshualitt9b989322014-12-15 14:16:27 -0800757
joshualittabb52a12015-01-13 15:02:10 -0800758 // Setup position
Brian Salomon7f235432017-08-16 09:41:48 -0400759 this->writeOutputPosition(vertBuilder,
760 uniformHandler,
761 gpArgs,
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500762 diegp.fInPosition.name(),
Brian Salomon7f235432017-08-16 09:41:48 -0400763 diegp.fViewMatrix,
764 &fViewMatrixUniform);
joshualittabb52a12015-01-13 15:02:10 -0800765
766 // emit transforms
egdaniel7ea439b2015-12-03 09:20:44 -0800767 this->emitTransforms(vertBuilder,
egdaniel0eafe792015-11-20 14:01:22 -0800768 varyingHandler,
egdaniel7ea439b2015-12-03 09:20:44 -0800769 uniformHandler,
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500770 diegp.fInPosition.asShaderVar(),
bsalomona624bf32016-09-20 09:12:47 -0700771 args.fFPCoordTransformHandler);
joshualitt4973d9d2014-11-08 09:24:25 -0800772
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000773 // for outer curve
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400774 fragBuilder->codeAppendf("float2 scaledOffset = %s.xy;", offsets0.fsIn());
775 fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;");
776 fragBuilder->codeAppendf("float2 duvdx = dFdx(%s.xy);", offsets0.fsIn());
777 fragBuilder->codeAppendf("float2 duvdy = dFdy(%s.xy);", offsets0.fsIn());
Brian Salomon289e3d82016-12-14 15:52:56 -0500778 fragBuilder->codeAppendf(
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400779 "float2 grad = float2(%s.x*duvdx.x + %s.y*duvdx.y,"
780 " %s.x*duvdy.x + %s.y*duvdy.y);",
Brian Salomon289e3d82016-12-14 15:52:56 -0500781 offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn());
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400782 if (diegp.fUseScale) {
783 fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn());
784 }
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000785
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400786 fragBuilder->codeAppend("float grad_dot = 4.0*dot(grad, grad);");
commit-bot@chromium.org1b035d82014-04-09 17:11:09 +0000787 // avoid calling inversesqrt on zero.
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400788 if (args.fShaderCaps->floatIs32Bits()) {
789 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);");
790 } else {
791 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
792 }
793 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
794 if (diegp.fUseScale) {
795 fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
796 }
bsalomoncdaa97b2016-03-08 08:30:14 -0800797 if (DIEllipseStyle::kHairline == diegp.fStyle) {
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000798 // can probably do this with one step
Greg Daniel2655ede2019-04-10 00:49:28 +0000799 fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);");
800 fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);");
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000801 } else {
Greg Daniel2655ede2019-04-10 00:49:28 +0000802 fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000803 }
804
805 // for inner curve
bsalomoncdaa97b2016-03-08 08:30:14 -0800806 if (DIEllipseStyle::kStroke == diegp.fStyle) {
egdaniel4ca2e602015-11-18 08:01:26 -0800807 fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn());
808 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;");
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400809 fragBuilder->codeAppendf("duvdx = float2(dFdx(%s));", offsets1.fsIn());
810 fragBuilder->codeAppendf("duvdy = float2(dFdy(%s));", offsets1.fsIn());
Brian Salomon289e3d82016-12-14 15:52:56 -0500811 fragBuilder->codeAppendf(
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400812 "grad = float2(%s.x*duvdx.x + %s.y*duvdx.y,"
813 " %s.x*duvdy.x + %s.y*duvdy.y);",
Brian Salomon289e3d82016-12-14 15:52:56 -0500814 offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn());
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400815 if (diegp.fUseScale) {
816 fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn());
817 }
818 fragBuilder->codeAppend("grad_dot = 4.0*dot(grad, grad);");
819 if (!args.fShaderCaps->floatIs32Bits()) {
820 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
821 }
822 fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
823 if (diegp.fUseScale) {
824 fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
825 }
Greg Daniel2655ede2019-04-10 00:49:28 +0000826 fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000827 }
Brian Salomonea26d6b2018-01-23 20:33:21 +0000828
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400829 fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000830 }
831
robertphillips46d36f02015-01-18 08:14:14 -0800832 static void GenKey(const GrGeometryProcessor& gp,
Brian Salomon94efbf52016-11-29 13:43:05 -0500833 const GrShaderCaps&,
joshualittb0a8a372014-09-23 09:50:21 -0700834 GrProcessorKeyBuilder* b) {
bsalomoncdaa97b2016-03-08 08:30:14 -0800835 const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
Greg Daniel2655ede2019-04-10 00:49:28 +0000836 uint16_t key = static_cast<uint16_t>(diegp.fStyle);
837 key |= ComputePosKey(diegp.fViewMatrix) << 10;
joshualittb8c241a2015-05-19 08:23:30 -0700838 b->add32(key);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000839 }
840
bsalomona624bf32016-09-20 09:12:47 -0700841 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
842 FPCoordTransformIter&& transformIter) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800843 const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
joshualitt5559ca22015-05-21 15:50:36 -0700844
bsalomon31df31c2016-08-17 09:00:24 -0700845 if (!diegp.fViewMatrix.isIdentity() && !fViewMatrix.cheapEqualTo(diegp.fViewMatrix)) {
846 fViewMatrix = diegp.fViewMatrix;
egdaniel018fb622015-10-28 07:26:40 -0700847 float viewMatrix[3 * 3];
egdaniel64c47282015-11-13 06:54:19 -0800848 GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
joshualitt5559ca22015-05-21 15:50:36 -0700849 pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
850 }
bsalomona624bf32016-09-20 09:12:47 -0700851 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000852 }
853
854 private:
joshualitt5559ca22015-05-21 15:50:36 -0700855 SkMatrix fViewMatrix;
joshualitt5559ca22015-05-21 15:50:36 -0700856 UniformHandle fViewMatrixUniform;
joshualitt9b989322014-12-15 14:16:27 -0800857
egdaniele659a582015-11-13 09:55:43 -0800858 typedef GrGLSLGeometryProcessor INHERITED;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000859 };
860
Brian Salomon92be2f72018-06-19 14:33:47 -0400861
Brian Osmanf04fb3c2018-11-12 15:34:00 -0500862 Attribute fInPosition;
863 Attribute fInColor;
864 Attribute fInEllipseOffsets0;
865 Attribute fInEllipseOffsets1;
Brian Salomon92be2f72018-06-19 14:33:47 -0400866
Brian Salomon289e3d82016-12-14 15:52:56 -0500867 SkMatrix fViewMatrix;
Jim Van Verth20ae25c2019-03-29 08:50:41 -0400868 bool fUseScale;
Brian Salomon289e3d82016-12-14 15:52:56 -0500869 DIEllipseStyle fStyle;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000870
Brian Salomon0c26a9d2017-07-06 10:09:38 -0400871 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000872
joshualitt249af152014-09-15 11:41:13 -0700873 typedef GrGeometryProcessor INHERITED;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000874};
875
bsalomoncdaa97b2016-03-08 08:30:14 -0800876GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000877
Hal Canary6f6961e2017-01-31 13:50:44 -0500878#if GR_TEST_UTILS
bungeman06ca8ec2016-06-09 08:01:03 -0700879sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500880 return sk_sp<GrGeometryProcessor>(new DIEllipseGeometryProcessor(
Greg Daniel2655ede2019-04-10 00:49:28 +0000881 d->fRandom->nextBool(), d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom),
882 (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2))));
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000883}
Hal Canary6f6961e2017-01-31 13:50:44 -0500884#endif
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000885
886///////////////////////////////////////////////////////////////////////////////
887
jvanverth6ca48822016-10-07 06:57:32 -0700888// We have two possible cases for geometry for a circle:
889
890// In the case of a normal fill, we draw geometry for the circle as an octagon.
891static const uint16_t gFillCircleIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -0500892 // enter the octagon
893 // clang-format off
894 0, 1, 8, 1, 2, 8,
895 2, 3, 8, 3, 4, 8,
896 4, 5, 8, 5, 6, 8,
897 6, 7, 8, 7, 0, 8
898 // clang-format on
jvanverth6ca48822016-10-07 06:57:32 -0700899};
900
901// For stroked circles, we use two nested octagons.
902static const uint16_t gStrokeCircleIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -0500903 // enter the octagon
904 // clang-format off
905 0, 1, 9, 0, 9, 8,
906 1, 2, 10, 1, 10, 9,
907 2, 3, 11, 2, 11, 10,
908 3, 4, 12, 3, 12, 11,
909 4, 5, 13, 4, 13, 12,
910 5, 6, 14, 5, 14, 13,
911 6, 7, 15, 6, 15, 14,
912 7, 0, 8, 7, 8, 15,
913 // clang-format on
jvanverth6ca48822016-10-07 06:57:32 -0700914};
915
Brian Osman9d958b52018-11-13 12:46:56 -0500916// Normalized geometry for octagons that circumscribe and lie on a circle:
917
918static constexpr SkScalar kOctOffset = 0.41421356237f; // sqrt(2) - 1
919static constexpr SkPoint kOctagonOuter[] = {
920 SkPoint::Make(-kOctOffset, -1),
921 SkPoint::Make( kOctOffset, -1),
922 SkPoint::Make( 1, -kOctOffset),
923 SkPoint::Make( 1, kOctOffset),
924 SkPoint::Make( kOctOffset, 1),
925 SkPoint::Make(-kOctOffset, 1),
926 SkPoint::Make(-1, kOctOffset),
927 SkPoint::Make(-1, -kOctOffset),
928};
929
930// cosine and sine of pi/8
931static constexpr SkScalar kCosPi8 = 0.923579533f;
932static constexpr SkScalar kSinPi8 = 0.382683432f;
933static constexpr SkPoint kOctagonInner[] = {
934 SkPoint::Make(-kSinPi8, -kCosPi8),
935 SkPoint::Make( kSinPi8, -kCosPi8),
936 SkPoint::Make( kCosPi8, -kSinPi8),
937 SkPoint::Make( kCosPi8, kSinPi8),
938 SkPoint::Make( kSinPi8, kCosPi8),
939 SkPoint::Make(-kSinPi8, kCosPi8),
940 SkPoint::Make(-kCosPi8, kSinPi8),
941 SkPoint::Make(-kCosPi8, -kSinPi8),
942};
Brian Salomon289e3d82016-12-14 15:52:56 -0500943
jvanverth6ca48822016-10-07 06:57:32 -0700944static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
945static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
946static const int kVertsPerStrokeCircle = 16;
947static const int kVertsPerFillCircle = 9;
948
949static int circle_type_to_vert_count(bool stroked) {
950 return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
951}
952
953static int circle_type_to_index_count(bool stroked) {
954 return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
955}
956
957static const uint16_t* circle_type_to_indices(bool stroked) {
958 return stroked ? gStrokeCircleIndices : gFillCircleIndices;
959}
960
961///////////////////////////////////////////////////////////////////////////////
962
Brian Salomon05441c42017-05-15 16:45:49 -0400963class CircleOp final : public GrMeshDrawOp {
964private:
965 using Helper = GrSimpleMeshDrawOpHelper;
966
joshualitt76e7fb62015-02-11 08:52:27 -0800967public:
Brian Salomon25a88092016-12-01 09:36:50 -0500968 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -0700969
bsalomon4f3a0ca2016-08-22 13:14:26 -0700970 /** Optional extra params to render a partial arc rather than a full circle. */
971 struct ArcParams {
972 SkScalar fStartAngleRadians;
973 SkScalar fSweepAngleRadians;
974 bool fUseCenter;
975 };
Brian Salomon05441c42017-05-15 16:45:49 -0400976
Robert Phillipsb97da532019-02-12 15:24:12 -0500977 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -0400978 GrPaint&& paint,
979 const SkMatrix& viewMatrix,
980 SkPoint center,
981 SkScalar radius,
982 const GrStyle& style,
Brian Salomon05441c42017-05-15 16:45:49 -0400983 const ArcParams* arcParams = nullptr) {
bsalomon4f3a0ca2016-08-22 13:14:26 -0700984 SkASSERT(circle_stays_circle(viewMatrix));
bsalomon4f3a0ca2016-08-22 13:14:26 -0700985 if (style.hasPathEffect()) {
986 return nullptr;
987 }
Brian Salomon05441c42017-05-15 16:45:49 -0400988 const SkStrokeRec& stroke = style.strokeRec();
bsalomon4f3a0ca2016-08-22 13:14:26 -0700989 SkStrokeRec::Style recStyle = stroke.getStyle();
990 if (arcParams) {
991 // Arc support depends on the style.
992 switch (recStyle) {
993 case SkStrokeRec::kStrokeAndFill_Style:
Brian Salomon289e3d82016-12-14 15:52:56 -0500994 // This produces a strange result that this op doesn't implement.
bsalomon4f3a0ca2016-08-22 13:14:26 -0700995 return nullptr;
996 case SkStrokeRec::kFill_Style:
997 // This supports all fills.
998 break;
Brian Salomon45c92202018-04-10 10:53:58 -0400999 case SkStrokeRec::kStroke_Style:
1000 // Strokes that don't use the center point are supported with butt and round
1001 // caps.
1002 if (arcParams->fUseCenter || stroke.getCap() == SkPaint::kSquare_Cap) {
1003 return nullptr;
1004 }
1005 break;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001006 case SkStrokeRec::kHairline_Style:
Brian Salomon45c92202018-04-10 10:53:58 -04001007 // Hairline only supports butt cap. Round caps could be emulated by slightly
1008 // extending the angle range if we ever care to.
bsalomon4f3a0ca2016-08-22 13:14:26 -07001009 if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) {
1010 return nullptr;
1011 }
1012 break;
1013 }
1014 }
Greg Daniel2655ede2019-04-10 00:49:28 +00001015 return Helper::FactoryHelper<CircleOp>(context, std::move(paint), viewMatrix, center,
1016 radius, style, arcParams);
Brian Salomon05441c42017-05-15 16:45:49 -04001017 }
1018
Greg Daniel2655ede2019-04-10 00:49:28 +00001019 CircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osmancf860852018-10-31 14:04:39 -04001020 const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, const GrStyle& style,
1021 const ArcParams* arcParams)
Brian Salomonea26d6b2018-01-23 20:33:21 +00001022 : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
Brian Salomon05441c42017-05-15 16:45:49 -04001023 const SkStrokeRec& stroke = style.strokeRec();
1024 SkStrokeRec::Style recStyle = stroke.getStyle();
bsalomon4f3a0ca2016-08-22 13:14:26 -07001025
Brian Salomon45c92202018-04-10 10:53:58 -04001026 fRoundCaps = false;
Greg Daniel2655ede2019-04-10 00:49:28 +00001027
bsalomon4b4a7cc2016-07-08 04:42:54 -07001028 viewMatrix.mapPoints(&center, 1);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001029 radius = viewMatrix.mapRadius(radius);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001030 SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
joshualitt76e7fb62015-02-11 08:52:27 -08001031
Brian Salomon289e3d82016-12-14 15:52:56 -05001032 bool isStrokeOnly =
1033 SkStrokeRec::kStroke_Style == recStyle || SkStrokeRec::kHairline_Style == recStyle;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001034 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001035
jvanverth6ca48822016-10-07 06:57:32 -07001036 SkScalar innerRadius = -SK_ScalarHalf;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001037 SkScalar outerRadius = radius;
1038 SkScalar halfWidth = 0;
1039 if (hasStroke) {
1040 if (SkScalarNearlyZero(strokeWidth)) {
1041 halfWidth = SK_ScalarHalf;
1042 } else {
1043 halfWidth = SkScalarHalf(strokeWidth);
1044 }
1045
1046 outerRadius += halfWidth;
1047 if (isStrokeOnly) {
1048 innerRadius = radius - halfWidth;
1049 }
1050 }
1051
1052 // The radii are outset for two reasons. First, it allows the shader to simply perform
1053 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
1054 // Second, the outer radius is used to compute the verts of the bounding box that is
1055 // rendered and the outset ensures the box will cover all partially covered by the circle.
Greg Daniel2655ede2019-04-10 00:49:28 +00001056 outerRadius += SK_ScalarHalf;
1057 innerRadius -= SK_ScalarHalf;
jvanverth6ca48822016-10-07 06:57:32 -07001058 bool stroked = isStrokeOnly && innerRadius > 0.0f;
Brian Salomon05441c42017-05-15 16:45:49 -04001059 fViewMatrixIfUsingLocalCoords = viewMatrix;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001060
bsalomon4f3a0ca2016-08-22 13:14:26 -07001061 // This makes every point fully inside the intersection plane.
1062 static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f};
1063 // This makes every point fully outside the union plane.
1064 static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f};
Brian Salomon45c92202018-04-10 10:53:58 -04001065 static constexpr SkPoint kUnusedRoundCaps[] = {{1e10f, 1e10f}, {1e10f, 1e10f}};
bsalomon4f3a0ca2016-08-22 13:14:26 -07001066 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
1067 center.fX + outerRadius, center.fY + outerRadius);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001068 if (arcParams) {
1069 // The shader operates in a space where the circle is translated to be centered at the
1070 // origin. Here we compute points on the unit circle at the starting and ending angles.
1071 SkPoint startPoint, stopPoint;
Brian Osman4428f2c2019-04-02 10:59:28 -04001072 startPoint.fY = SkScalarSin(arcParams->fStartAngleRadians);
1073 startPoint.fX = SkScalarCos(arcParams->fStartAngleRadians);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001074 SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians;
Brian Osman4428f2c2019-04-02 10:59:28 -04001075 stopPoint.fY = SkScalarSin(endAngle);
1076 stopPoint.fX = SkScalarCos(endAngle);
Brian Osmanfd773fb2017-05-17 11:43:11 -04001077
1078 // Adjust the start and end points based on the view matrix (to handle rotated arcs)
1079 startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY);
1080 stopPoint = viewMatrix.mapVector(stopPoint.fX, stopPoint.fY);
1081 startPoint.normalize();
1082 stopPoint.normalize();
1083
1084 // If the matrix included scale (on one axis) we need to swap our start and end points
1085 if ((viewMatrix.getScaleX() < 0) != (viewMatrix.getScaleY() < 0)) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04001086 using std::swap;
1087 swap(startPoint, stopPoint);
Brian Osmanfd773fb2017-05-17 11:43:11 -04001088 }
1089
Brian Salomon45c92202018-04-10 10:53:58 -04001090 fRoundCaps = style.strokeRec().getWidth() > 0 &&
1091 style.strokeRec().getCap() == SkPaint::kRound_Cap;
1092 SkPoint roundCaps[2];
1093 if (fRoundCaps) {
1094 // Compute the cap center points in the normalized space.
1095 SkScalar midRadius = (innerRadius + outerRadius) / (2 * outerRadius);
1096 roundCaps[0] = startPoint * midRadius;
1097 roundCaps[1] = stopPoint * midRadius;
1098 } else {
1099 roundCaps[0] = kUnusedRoundCaps[0];
1100 roundCaps[1] = kUnusedRoundCaps[1];
1101 }
1102
bsalomon4f3a0ca2016-08-22 13:14:26 -07001103 // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against
Brian Salomon45c92202018-04-10 10:53:58 -04001104 // radial lines. We treat round caps the same way, but tack coverage of circles at the
1105 // center of the butts.
1106 // However, in both cases we have to be careful about the half-circle.
bsalomon4f3a0ca2016-08-22 13:14:26 -07001107 // case. In that case the two radial lines are equal and so that edge gets clipped
Brian Salomon45c92202018-04-10 10:53:58 -04001108 // twice. Since the shared edge goes through the center we fall back on the !useCenter
bsalomon4f3a0ca2016-08-22 13:14:26 -07001109 // case.
Brian Salomon45c92202018-04-10 10:53:58 -04001110 auto absSweep = SkScalarAbs(arcParams->fSweepAngleRadians);
1111 bool useCenter = (arcParams->fUseCenter || isStrokeOnly) &&
1112 !SkScalarNearlyEqual(absSweep, SK_ScalarPI);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001113 if (useCenter) {
1114 SkVector norm0 = {startPoint.fY, -startPoint.fX};
1115 SkVector norm1 = {stopPoint.fY, -stopPoint.fX};
Brian Osman9a24fee2018-08-03 09:48:42 -04001116 // This ensures that norm0 is always the clockwise plane, and norm1 is CCW.
1117 if (arcParams->fSweepAngleRadians < 0) {
1118 std::swap(norm0, norm1);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001119 }
Brian Osman9a24fee2018-08-03 09:48:42 -04001120 norm0.negate();
Brian Salomon05441c42017-05-15 16:45:49 -04001121 fClipPlane = true;
Brian Salomon45c92202018-04-10 10:53:58 -04001122 if (absSweep > SK_ScalarPI) {
Brian Salomon05441c42017-05-15 16:45:49 -04001123 fCircles.emplace_back(Circle{
bsalomon4f3a0ca2016-08-22 13:14:26 -07001124 color,
1125 innerRadius,
1126 outerRadius,
1127 {norm0.fX, norm0.fY, 0.5f},
1128 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1129 {norm1.fX, norm1.fY, 0.5f},
Brian Salomon45c92202018-04-10 10:53:58 -04001130 {roundCaps[0], roundCaps[1]},
jvanverth6ca48822016-10-07 06:57:32 -07001131 devBounds,
Brian Salomon289e3d82016-12-14 15:52:56 -05001132 stroked});
Brian Salomon05441c42017-05-15 16:45:49 -04001133 fClipPlaneIsect = false;
1134 fClipPlaneUnion = true;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001135 } else {
Brian Salomon05441c42017-05-15 16:45:49 -04001136 fCircles.emplace_back(Circle{
bsalomon4f3a0ca2016-08-22 13:14:26 -07001137 color,
1138 innerRadius,
1139 outerRadius,
1140 {norm0.fX, norm0.fY, 0.5f},
1141 {norm1.fX, norm1.fY, 0.5f},
1142 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
Brian Salomon45c92202018-04-10 10:53:58 -04001143 {roundCaps[0], roundCaps[1]},
jvanverth6ca48822016-10-07 06:57:32 -07001144 devBounds,
Brian Salomon289e3d82016-12-14 15:52:56 -05001145 stroked});
Brian Salomon05441c42017-05-15 16:45:49 -04001146 fClipPlaneIsect = true;
1147 fClipPlaneUnion = false;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001148 }
1149 } else {
1150 // We clip to a secant of the original circle.
1151 startPoint.scale(radius);
1152 stopPoint.scale(radius);
1153 SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX};
1154 norm.normalize();
1155 if (arcParams->fSweepAngleRadians > 0) {
1156 norm.negate();
1157 }
1158 SkScalar d = -norm.dot(startPoint) + 0.5f;
1159
Brian Salomon05441c42017-05-15 16:45:49 -04001160 fCircles.emplace_back(
1161 Circle{color,
1162 innerRadius,
1163 outerRadius,
1164 {norm.fX, norm.fY, d},
1165 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1166 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
Brian Salomon45c92202018-04-10 10:53:58 -04001167 {roundCaps[0], roundCaps[1]},
Brian Salomon05441c42017-05-15 16:45:49 -04001168 devBounds,
1169 stroked});
1170 fClipPlane = true;
1171 fClipPlaneIsect = false;
1172 fClipPlaneUnion = false;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001173 }
1174 } else {
Brian Salomon05441c42017-05-15 16:45:49 -04001175 fCircles.emplace_back(
1176 Circle{color,
1177 innerRadius,
1178 outerRadius,
1179 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1180 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1181 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
Brian Salomon45c92202018-04-10 10:53:58 -04001182 {kUnusedRoundCaps[0], kUnusedRoundCaps[1]},
Brian Salomon05441c42017-05-15 16:45:49 -04001183 devBounds,
1184 stroked});
1185 fClipPlane = false;
1186 fClipPlaneIsect = false;
1187 fClipPlaneUnion = false;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001188 }
bsalomon88cf17d2016-07-08 06:40:56 -07001189 // Use the original radius and stroke radius for the bounds so that it does not include the
1190 // AA bloat.
1191 radius += halfWidth;
Brian Salomon05441c42017-05-15 16:45:49 -04001192 this->setBounds(
Brian Salomon289e3d82016-12-14 15:52:56 -05001193 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
Greg Daniel2655ede2019-04-10 00:49:28 +00001194 HasAABloat::kYes, IsZeroArea::kNo);
Brian Salomon05441c42017-05-15 16:45:49 -04001195 fVertCount = circle_type_to_vert_count(stroked);
1196 fIndexCount = circle_type_to_index_count(stroked);
1197 fAllFill = !stroked;
bsalomoncdaa97b2016-03-08 08:30:14 -08001198 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07001199
Brian Salomon289e3d82016-12-14 15:52:56 -05001200 const char* name() const override { return "CircleOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08001201
Chris Dalton1706cbf2019-05-21 19:35:29 -06001202 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001203 fHelper.visitProxies(func);
1204 }
1205
Brian Osman9a390ac2018-11-12 09:47:48 -05001206#ifdef SK_DEBUG
robertphillipse004bfc2015-11-16 09:06:59 -08001207 SkString dumpInfo() const override {
1208 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04001209 for (int i = 0; i < fCircles.count(); ++i) {
Brian Salomon289e3d82016-12-14 15:52:56 -05001210 string.appendf(
1211 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1212 "InnerRad: %.2f, OuterRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04001213 fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft,
Brian Osman1be2b7c2018-10-29 16:07:15 -04001214 fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight,
1215 fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius,
1216 fCircles[i].fOuterRadius);
robertphillipse004bfc2015-11-16 09:06:59 -08001217 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04001218 string += fHelper.dumpInfo();
1219 string += INHERITED::dumpInfo();
robertphillipse004bfc2015-11-16 09:06:59 -08001220 return string;
1221 }
Brian Osman9a390ac2018-11-12 09:47:48 -05001222#endif
robertphillipse004bfc2015-11-16 09:06:59 -08001223
Chris Dalton6ce447a2019-06-23 18:07:38 -06001224 GrProcessorSet::Analysis finalize(
1225 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
1226 GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04001227 SkPMColor4f* color = &fCircles.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06001228 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04001229 GrProcessorAnalysisCoverage::kSingleChannel, color,
1230 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001231 }
1232
1233 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1234
bsalomone46f9fe2015-08-18 06:05:14 -07001235private:
Brian Salomon91326c32017-08-09 16:02:19 -04001236 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08001237 SkMatrix localMatrix;
1238 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001239 return;
1240 }
1241
1242 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05001243 sk_sp<GrGeometryProcessor> gp(new CircleGeometryProcessor(
Greg Daniel2655ede2019-04-10 00:49:28 +00001244 !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, fRoundCaps, fWideColor,
1245 localMatrix));
bsalomon4f3a0ca2016-08-22 13:14:26 -07001246
Brian Salomon12d22642019-01-29 14:38:50 -05001247 sk_sp<const GrBuffer> vertexBuffer;
jvanverth6ca48822016-10-07 06:57:32 -07001248 int firstVertex;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001249 GrVertexWriter vertices{target->makeVertexSpace(gp->vertexStride(), fVertCount,
1250 &vertexBuffer, &firstVertex)};
1251 if (!vertices.fPtr) {
jvanverth6ca48822016-10-07 06:57:32 -07001252 SkDebugf("Could not allocate vertices\n");
joshualitt4b31de82015-03-05 14:33:41 -08001253 return;
1254 }
1255
Brian Salomon12d22642019-01-29 14:38:50 -05001256 sk_sp<const GrBuffer> indexBuffer = nullptr;
jvanverth6ca48822016-10-07 06:57:32 -07001257 int firstIndex = 0;
1258 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1259 if (!indices) {
1260 SkDebugf("Could not allocate indices\n");
1261 return;
1262 }
1263
1264 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04001265 for (const auto& circle : fCircles) {
1266 SkScalar innerRadius = circle.fInnerRadius;
1267 SkScalar outerRadius = circle.fOuterRadius;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001268 GrVertexColor color(circle.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001269 const SkRect& bounds = circle.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08001270
joshualitt76e7fb62015-02-11 08:52:27 -08001271 // The inner radius in the vertex data must be specified in normalized space.
1272 innerRadius = innerRadius / outerRadius;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001273 SkPoint radii = { outerRadius, innerRadius };
jvanverth6ca48822016-10-07 06:57:32 -07001274
1275 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
Brian Salomon289e3d82016-12-14 15:52:56 -05001276 SkScalar halfWidth = 0.5f * bounds.width();
Herb Derby60c05f92016-12-13 15:18:55 -05001277
Brian Osman9a24fee2018-08-03 09:48:42 -04001278 SkVector geoClipPlane = { 0, 0 };
1279 SkScalar offsetClipDist = SK_Scalar1;
1280 if (!circle.fStroked && fClipPlane && fClipPlaneIsect &&
1281 (circle.fClipPlane[0] * circle.fIsectPlane[0] +
1282 circle.fClipPlane[1] * circle.fIsectPlane[1]) < 0.0f) {
1283 // Acute arc. Clip the vertices to the perpendicular half-plane. We've constructed
1284 // fClipPlane to be clockwise, and fISectPlane to be CCW, so we can can rotate them
1285 // each 90 degrees to point "out", then average them. We back off by 1/2 pixel so
1286 // the AA can extend just past the center of the circle.
1287 geoClipPlane.set(circle.fClipPlane[1] - circle.fIsectPlane[1],
1288 circle.fIsectPlane[0] - circle.fClipPlane[0]);
1289 SkAssertResult(geoClipPlane.normalize());
1290 offsetClipDist = 0.5f / halfWidth;
1291 }
1292
Brian Osman7d8f82b2018-11-08 10:24:09 -05001293 for (int i = 0; i < 8; ++i) {
1294 // This clips the normalized offset to the half-plane we computed above. Then we
1295 // compute the vertex position from this.
Brian Osman9d958b52018-11-13 12:46:56 -05001296 SkScalar dist = SkTMin(kOctagonOuter[i].dot(geoClipPlane) + offsetClipDist, 0.0f);
1297 SkVector offset = kOctagonOuter[i] - geoClipPlane * dist;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001298 vertices.write(center + offset * halfWidth,
1299 color,
1300 offset,
1301 radii);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001302 if (fClipPlane) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001303 vertices.write(circle.fClipPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001304 }
1305 if (fClipPlaneIsect) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001306 vertices.write(circle.fIsectPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001307 }
1308 if (fClipPlaneUnion) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001309 vertices.write(circle.fUnionPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001310 }
1311 if (fRoundCaps) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001312 vertices.write(circle.fRoundCapCenters);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001313 }
Brian Salomon45c92202018-04-10 10:53:58 -04001314 }
jvanverth6ca48822016-10-07 06:57:32 -07001315
Brian Salomon05441c42017-05-15 16:45:49 -04001316 if (circle.fStroked) {
jvanverth6ca48822016-10-07 06:57:32 -07001317 // compute the inner ring
jvanverth6ca48822016-10-07 06:57:32 -07001318
Brian Osman7d8f82b2018-11-08 10:24:09 -05001319 for (int i = 0; i < 8; ++i) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001320 vertices.write(center + kOctagonInner[i] * circle.fInnerRadius,
1321 color,
1322 kOctagonInner[i] * innerRadius,
1323 radii);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001324 if (fClipPlane) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001325 vertices.write(circle.fClipPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001326 }
1327 if (fClipPlaneIsect) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001328 vertices.write(circle.fIsectPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001329 }
1330 if (fClipPlaneUnion) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001331 vertices.write(circle.fUnionPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001332 }
1333 if (fRoundCaps) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001334 vertices.write(circle.fRoundCapCenters);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001335 }
Brian Salomon45c92202018-04-10 10:53:58 -04001336 }
jvanverth6ca48822016-10-07 06:57:32 -07001337 } else {
1338 // filled
Brian Osmanf9aabff2018-11-13 16:11:38 -05001339 vertices.write(center, color, SkPoint::Make(0, 0), radii);
jvanverth6ca48822016-10-07 06:57:32 -07001340 if (fClipPlane) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001341 vertices.write(circle.fClipPlane);
jvanverth6ca48822016-10-07 06:57:32 -07001342 }
jvanverth6ca48822016-10-07 06:57:32 -07001343 if (fClipPlaneIsect) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001344 vertices.write(circle.fIsectPlane);
jvanverth6ca48822016-10-07 06:57:32 -07001345 }
1346 if (fClipPlaneUnion) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001347 vertices.write(circle.fUnionPlane);
jvanverth6ca48822016-10-07 06:57:32 -07001348 }
Brian Salomon5ceda552018-12-14 16:03:38 -05001349 if (fRoundCaps) {
1350 vertices.write(circle.fRoundCapCenters);
1351 }
jvanverth6ca48822016-10-07 06:57:32 -07001352 }
1353
Brian Salomon05441c42017-05-15 16:45:49 -04001354 const uint16_t* primIndices = circle_type_to_indices(circle.fStroked);
1355 const int primIndexCount = circle_type_to_index_count(circle.fStroked);
jvanverth6ca48822016-10-07 06:57:32 -07001356 for (int i = 0; i < primIndexCount; ++i) {
1357 *indices++ = primIndices[i] + currStartVertex;
1358 }
1359
Brian Salomon05441c42017-05-15 16:45:49 -04001360 currStartVertex += circle_type_to_vert_count(circle.fStroked);
joshualitt76e7fb62015-02-11 08:52:27 -08001361 }
jvanverth6ca48822016-10-07 06:57:32 -07001362
Brian Salomon7eae3e02018-08-07 14:02:38 +00001363 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05001364 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00001365 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05001366 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07001367 target->recordDraw(std::move(gp), mesh);
1368 }
1369
1370 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1371 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08001372 }
1373
Brian Salomon7eae3e02018-08-07 14:02:38 +00001374 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05001375 CircleOp* that = t->cast<CircleOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05001376
1377 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05001378 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001379 return CombineResult::kCannotCombine;
Jim Van Verth8cefe402017-02-09 11:36:37 -05001380 }
1381
Brian Salomon05441c42017-05-15 16:45:49 -04001382 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001383 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07001384 }
1385
Brian Salomon05441c42017-05-15 16:45:49 -04001386 if (fHelper.usesLocalCoords() &&
1387 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001388 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08001389 }
1390
Brian Salomon289e3d82016-12-14 15:52:56 -05001391 // Because we've set up the ops that don't use the planes with noop values
1392 // we can just accumulate used planes by later ops.
bsalomon4f3a0ca2016-08-22 13:14:26 -07001393 fClipPlane |= that->fClipPlane;
1394 fClipPlaneIsect |= that->fClipPlaneIsect;
1395 fClipPlaneUnion |= that->fClipPlaneUnion;
Brian Salomon45c92202018-04-10 10:53:58 -04001396 fRoundCaps |= that->fRoundCaps;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001397 fWideColor |= that->fWideColor;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001398
Brian Salomon05441c42017-05-15 16:45:49 -04001399 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
jvanverth6ca48822016-10-07 06:57:32 -07001400 fVertCount += that->fVertCount;
1401 fIndexCount += that->fIndexCount;
1402 fAllFill = fAllFill && that->fAllFill;
Brian Salomon7eae3e02018-08-07 14:02:38 +00001403 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08001404 }
1405
Brian Salomon05441c42017-05-15 16:45:49 -04001406 struct Circle {
Brian Osmancf860852018-10-31 14:04:39 -04001407 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001408 SkScalar fInnerRadius;
1409 SkScalar fOuterRadius;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001410 SkScalar fClipPlane[3];
1411 SkScalar fIsectPlane[3];
1412 SkScalar fUnionPlane[3];
Brian Salomon45c92202018-04-10 10:53:58 -04001413 SkPoint fRoundCapCenters[2];
Brian Salomon289e3d82016-12-14 15:52:56 -05001414 SkRect fDevBounds;
1415 bool fStroked;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001416 };
1417
Brian Salomon289e3d82016-12-14 15:52:56 -05001418 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04001419 Helper fHelper;
1420 SkSTArray<1, Circle, true> fCircles;
Brian Salomon289e3d82016-12-14 15:52:56 -05001421 int fVertCount;
1422 int fIndexCount;
1423 bool fAllFill;
1424 bool fClipPlane;
1425 bool fClipPlaneIsect;
1426 bool fClipPlaneUnion;
Brian Salomon45c92202018-04-10 10:53:58 -04001427 bool fRoundCaps;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001428 bool fWideColor;
reed1b55a962015-09-17 20:16:13 -07001429
Brian Salomon05441c42017-05-15 16:45:49 -04001430 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08001431};
1432
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001433class ButtCapDashedCircleOp final : public GrMeshDrawOp {
1434private:
1435 using Helper = GrSimpleMeshDrawOpHelper;
1436
1437public:
1438 DEFINE_OP_CLASS_ID
1439
Robert Phillipsb97da532019-02-12 15:24:12 -05001440 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04001441 GrPaint&& paint,
1442 const SkMatrix& viewMatrix,
1443 SkPoint center,
1444 SkScalar radius,
1445 SkScalar strokeWidth,
1446 SkScalar startAngle,
1447 SkScalar onAngle,
1448 SkScalar offAngle,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001449 SkScalar phaseAngle) {
1450 SkASSERT(circle_stays_circle(viewMatrix));
1451 SkASSERT(strokeWidth < 2 * radius);
Robert Phillips7c525e62018-06-12 10:11:12 -04001452 return Helper::FactoryHelper<ButtCapDashedCircleOp>(context, std::move(paint), viewMatrix,
1453 center, radius, strokeWidth, startAngle,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001454 onAngle, offAngle, phaseAngle);
1455 }
1456
Brian Osmancf860852018-10-31 14:04:39 -04001457 ButtCapDashedCircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001458 const SkMatrix& viewMatrix, SkPoint center, SkScalar radius,
1459 SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle,
1460 SkScalar offAngle, SkScalar phaseAngle)
1461 : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
1462 SkASSERT(circle_stays_circle(viewMatrix));
1463 viewMatrix.mapPoints(&center, 1);
1464 radius = viewMatrix.mapRadius(radius);
1465 strokeWidth = viewMatrix.mapRadius(strokeWidth);
1466
1467 // Determine the angle where the circle starts in device space and whether its orientation
1468 // has been reversed.
1469 SkVector start;
1470 bool reflection;
1471 if (!startAngle) {
1472 start = {1, 0};
1473 } else {
Brian Osman4428f2c2019-04-02 10:59:28 -04001474 start.fY = SkScalarSin(startAngle);
1475 start.fX = SkScalarCos(startAngle);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001476 }
1477 viewMatrix.mapVectors(&start, 1);
1478 startAngle = SkScalarATan2(start.fY, start.fX);
1479 reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() -
1480 viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0;
1481
1482 auto totalAngle = onAngle + offAngle;
1483 phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2;
1484
1485 SkScalar halfWidth = 0;
1486 if (SkScalarNearlyZero(strokeWidth)) {
1487 halfWidth = SK_ScalarHalf;
1488 } else {
1489 halfWidth = SkScalarHalf(strokeWidth);
1490 }
1491
1492 SkScalar outerRadius = radius + halfWidth;
1493 SkScalar innerRadius = radius - halfWidth;
1494
1495 // The radii are outset for two reasons. First, it allows the shader to simply perform
1496 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
1497 // Second, the outer radius is used to compute the verts of the bounding box that is
1498 // rendered and the outset ensures the box will cover all partially covered by the circle.
1499 outerRadius += SK_ScalarHalf;
1500 innerRadius -= SK_ScalarHalf;
1501 fViewMatrixIfUsingLocalCoords = viewMatrix;
1502
1503 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
1504 center.fX + outerRadius, center.fY + outerRadius);
1505
1506 // We store whether there is a reflection as a negative total angle.
1507 if (reflection) {
1508 totalAngle = -totalAngle;
1509 }
1510 fCircles.push_back(Circle{
1511 color,
1512 outerRadius,
1513 innerRadius,
1514 onAngle,
1515 totalAngle,
1516 startAngle,
1517 phaseAngle,
1518 devBounds
1519 });
1520 // Use the original radius and stroke radius for the bounds so that it does not include the
1521 // AA bloat.
1522 radius += halfWidth;
1523 this->setBounds(
1524 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
1525 HasAABloat::kYes, IsZeroArea::kNo);
1526 fVertCount = circle_type_to_vert_count(true);
1527 fIndexCount = circle_type_to_index_count(true);
1528 }
1529
1530 const char* name() const override { return "ButtCappedDashedCircleOp"; }
1531
Chris Dalton1706cbf2019-05-21 19:35:29 -06001532 void visitProxies(const VisitProxyFunc& func) const override {
Brian Salomon7d94bb52018-10-12 14:37:19 -04001533 fHelper.visitProxies(func);
1534 }
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001535
Brian Osman9a390ac2018-11-12 09:47:48 -05001536#ifdef SK_DEBUG
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001537 SkString dumpInfo() const override {
1538 SkString string;
1539 for (int i = 0; i < fCircles.count(); ++i) {
1540 string.appendf(
1541 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1542 "InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, "
1543 "Phase: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04001544 fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft,
Brian Osman1be2b7c2018-10-29 16:07:15 -04001545 fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight,
1546 fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius,
1547 fCircles[i].fOuterRadius, fCircles[i].fOnAngle, fCircles[i].fTotalAngle,
1548 fCircles[i].fPhaseAngle);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001549 }
1550 string += fHelper.dumpInfo();
1551 string += INHERITED::dumpInfo();
1552 return string;
1553 }
Brian Osman9a390ac2018-11-12 09:47:48 -05001554#endif
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001555
Chris Dalton6ce447a2019-06-23 18:07:38 -06001556 GrProcessorSet::Analysis finalize(
1557 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
1558 GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04001559 SkPMColor4f* color = &fCircles.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06001560 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04001561 GrProcessorAnalysisCoverage::kSingleChannel, color,
1562 &fWideColor);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001563 }
1564
1565 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1566
1567private:
1568 void onPrepareDraws(Target* target) override {
1569 SkMatrix localMatrix;
1570 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
1571 return;
1572 }
1573
1574 // Setup geometry processor
Brian Osmane3caf2d2018-11-21 13:48:36 -05001575 sk_sp<GrGeometryProcessor> gp(new ButtCapDashedCircleGeometryProcessor(fWideColor,
1576 localMatrix));
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001577
Brian Salomon12d22642019-01-29 14:38:50 -05001578 sk_sp<const GrBuffer> vertexBuffer;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001579 int firstVertex;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001580 GrVertexWriter vertices{target->makeVertexSpace(gp->vertexStride(), fVertCount,
1581 &vertexBuffer, &firstVertex)};
1582 if (!vertices.fPtr) {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001583 SkDebugf("Could not allocate vertices\n");
1584 return;
1585 }
1586
Brian Salomon12d22642019-01-29 14:38:50 -05001587 sk_sp<const GrBuffer> indexBuffer;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001588 int firstIndex = 0;
1589 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1590 if (!indices) {
1591 SkDebugf("Could not allocate indices\n");
1592 return;
1593 }
1594
1595 int currStartVertex = 0;
1596 for (const auto& circle : fCircles) {
1597 // The inner radius in the vertex data must be specified in normalized space so that
1598 // length() can be called with smaller values to avoid precision issues with half
1599 // floats.
1600 auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius;
1601 const SkRect& bounds = circle.fDevBounds;
1602 bool reflect = false;
Brian Osman9d958b52018-11-13 12:46:56 -05001603 struct { float onAngle, totalAngle, startAngle, phaseAngle; } dashParams = {
1604 circle.fOnAngle, circle.fTotalAngle, circle.fStartAngle, circle.fPhaseAngle
1605 };
1606 if (dashParams.totalAngle < 0) {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001607 reflect = true;
Brian Osman9d958b52018-11-13 12:46:56 -05001608 dashParams.totalAngle = -dashParams.totalAngle;
1609 dashParams.startAngle = -dashParams.startAngle;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001610 }
1611
Brian Osmane3caf2d2018-11-21 13:48:36 -05001612 GrVertexColor color(circle.fColor, fWideColor);
Brian Osman1be2b7c2018-10-29 16:07:15 -04001613
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001614 // The bounding geometry for the circle is composed of an outer bounding octagon and
1615 // an inner bounded octagon.
1616
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001617 // Compute the vertices of the outer octagon.
1618 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
1619 SkScalar halfWidth = 0.5f * bounds.width();
Brian Osman9d958b52018-11-13 12:46:56 -05001620
1621 auto reflectY = [=](const SkPoint& p) {
1622 return SkPoint{ p.fX, reflect ? -p.fY : p.fY };
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001623 };
Brian Osman9d958b52018-11-13 12:46:56 -05001624
1625 for (int i = 0; i < 8; ++i) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001626 vertices.write(center + kOctagonOuter[i] * halfWidth,
1627 color,
1628 reflectY(kOctagonOuter[i]),
1629 circle.fOuterRadius,
1630 normInnerRadius,
1631 dashParams);
Brian Osman9d958b52018-11-13 12:46:56 -05001632 }
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001633
1634 // Compute the vertices of the inner octagon.
Brian Osman9d958b52018-11-13 12:46:56 -05001635 for (int i = 0; i < 8; ++i) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001636 vertices.write(center + kOctagonInner[i] * circle.fInnerRadius,
1637 color,
1638 reflectY(kOctagonInner[i]) * normInnerRadius,
1639 circle.fOuterRadius,
1640 normInnerRadius,
1641 dashParams);
Brian Osman9d958b52018-11-13 12:46:56 -05001642 }
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001643
1644 const uint16_t* primIndices = circle_type_to_indices(true);
1645 const int primIndexCount = circle_type_to_index_count(true);
1646 for (int i = 0; i < primIndexCount; ++i) {
1647 *indices++ = primIndices[i] + currStartVertex;
1648 }
1649
1650 currStartVertex += circle_type_to_vert_count(true);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001651 }
1652
Brian Salomon7eae3e02018-08-07 14:02:38 +00001653 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05001654 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00001655 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05001656 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07001657 target->recordDraw(std::move(gp), mesh);
1658 }
1659
1660 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1661 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001662 }
1663
Brian Salomon7eae3e02018-08-07 14:02:38 +00001664 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001665 ButtCapDashedCircleOp* that = t->cast<ButtCapDashedCircleOp>();
1666
1667 // can only represent 65535 unique vertices with 16-bit indices
1668 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001669 return CombineResult::kCannotCombine;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001670 }
1671
1672 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001673 return CombineResult::kCannotCombine;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001674 }
1675
1676 if (fHelper.usesLocalCoords() &&
1677 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001678 return CombineResult::kCannotCombine;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001679 }
1680
1681 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001682 fVertCount += that->fVertCount;
1683 fIndexCount += that->fIndexCount;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001684 fWideColor |= that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00001685 return CombineResult::kMerged;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001686 }
1687
1688 struct Circle {
Brian Osmancf860852018-10-31 14:04:39 -04001689 SkPMColor4f fColor;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001690 SkScalar fOuterRadius;
1691 SkScalar fInnerRadius;
1692 SkScalar fOnAngle;
1693 SkScalar fTotalAngle;
1694 SkScalar fStartAngle;
1695 SkScalar fPhaseAngle;
1696 SkRect fDevBounds;
1697 };
1698
1699 SkMatrix fViewMatrixIfUsingLocalCoords;
1700 Helper fHelper;
1701 SkSTArray<1, Circle, true> fCircles;
1702 int fVertCount;
1703 int fIndexCount;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001704 bool fWideColor;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001705
1706 typedef GrMeshDrawOp INHERITED;
1707};
1708
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00001709///////////////////////////////////////////////////////////////////////////////
1710
Brian Salomon05441c42017-05-15 16:45:49 -04001711class EllipseOp : public GrMeshDrawOp {
1712private:
1713 using Helper = GrSimpleMeshDrawOpHelper;
1714
1715 struct DeviceSpaceParams {
1716 SkPoint fCenter;
1717 SkScalar fXRadius;
1718 SkScalar fYRadius;
1719 SkScalar fInnerXRadius;
1720 SkScalar fInnerYRadius;
1721 };
1722
joshualitt76e7fb62015-02-11 08:52:27 -08001723public:
Brian Salomon25a88092016-12-01 09:36:50 -05001724 DEFINE_OP_CLASS_ID
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001725
Robert Phillipsb97da532019-02-12 15:24:12 -05001726 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04001727 GrPaint&& paint,
1728 const SkMatrix& viewMatrix,
1729 const SkRect& ellipse,
1730 const SkStrokeRec& stroke) {
Brian Salomon05441c42017-05-15 16:45:49 -04001731 DeviceSpaceParams params;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001732 // do any matrix crunching before we reset the draw state for device coords
Brian Salomon05441c42017-05-15 16:45:49 -04001733 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
1734 viewMatrix.mapPoints(&params.fCenter, 1);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001735 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width());
1736 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height());
Brian Salomon05441c42017-05-15 16:45:49 -04001737 params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius +
1738 viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius);
1739 params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius +
1740 viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08001741
bsalomon4b4a7cc2016-07-08 04:42:54 -07001742 // do (potentially) anisotropic mapping of stroke
1743 SkVector scaledStroke;
1744 SkScalar strokeWidth = stroke.getWidth();
Brian Salomon289e3d82016-12-14 15:52:56 -05001745 scaledStroke.fX = SkScalarAbs(
1746 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
1747 scaledStroke.fY = SkScalarAbs(
1748 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
bsalomon4b4a7cc2016-07-08 04:42:54 -07001749
1750 SkStrokeRec::Style style = stroke.getStyle();
Brian Salomon289e3d82016-12-14 15:52:56 -05001751 bool isStrokeOnly =
1752 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001753 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
1754
Brian Salomon05441c42017-05-15 16:45:49 -04001755 params.fInnerXRadius = 0;
1756 params.fInnerYRadius = 0;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001757 if (hasStroke) {
1758 if (SkScalarNearlyZero(scaledStroke.length())) {
1759 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
1760 } else {
1761 scaledStroke.scale(SK_ScalarHalf);
1762 }
1763
1764 // we only handle thick strokes for near-circular ellipses
1765 if (scaledStroke.length() > SK_ScalarHalf &&
Brian Salomon05441c42017-05-15 16:45:49 -04001766 (0.5f * params.fXRadius > params.fYRadius ||
1767 0.5f * params.fYRadius > params.fXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07001768 return nullptr;
1769 }
1770
1771 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon05441c42017-05-15 16:45:49 -04001772 if (scaledStroke.fX * (params.fXRadius * params.fYRadius) <
1773 (scaledStroke.fY * scaledStroke.fY) * params.fXRadius ||
1774 scaledStroke.fY * (params.fXRadius * params.fXRadius) <
1775 (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07001776 return nullptr;
1777 }
1778
1779 // this is legit only if scale & translation (which should be the case at the moment)
1780 if (isStrokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04001781 params.fInnerXRadius = params.fXRadius - scaledStroke.fX;
1782 params.fInnerYRadius = params.fYRadius - scaledStroke.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001783 }
1784
Brian Salomon05441c42017-05-15 16:45:49 -04001785 params.fXRadius += scaledStroke.fX;
1786 params.fYRadius += scaledStroke.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001787 }
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001788
1789 // For large ovals with low precision floats, we fall back to the path renderer.
1790 // To compute the AA at the edge we divide by the gradient, which is clamped to a
1791 // minimum value to avoid divides by zero. With large ovals and low precision this
1792 // leads to blurring at the edge of the oval.
1793 const SkScalar kMaxOvalRadius = 16384;
1794 if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
1795 (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
1796 return nullptr;
1797 }
1798
Greg Daniel2655ede2019-04-10 00:49:28 +00001799 return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04001800 params, stroke);
Brian Salomon05441c42017-05-15 16:45:49 -04001801 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07001802
Brian Osmancf860852018-10-31 14:04:39 -04001803 EllipseOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Greg Daniel2655ede2019-04-10 00:49:28 +00001804 const SkMatrix& viewMatrix, const DeviceSpaceParams& params,
Brian Osman936fe7d2018-10-30 15:30:35 -04001805 const SkStrokeRec& stroke)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001806 : INHERITED(ClassID())
1807 , fHelper(helperArgs, GrAAType::kCoverage)
1808 , fUseScale(false) {
Brian Salomon05441c42017-05-15 16:45:49 -04001809 SkStrokeRec::Style style = stroke.getStyle();
1810 bool isStrokeOnly =
1811 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001812
Brian Salomon05441c42017-05-15 16:45:49 -04001813 fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius,
1814 params.fInnerXRadius, params.fInnerYRadius,
1815 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius,
1816 params.fCenter.fY - params.fYRadius,
1817 params.fCenter.fX + params.fXRadius,
1818 params.fCenter.fY + params.fYRadius)});
1819
Greg Daniel2655ede2019-04-10 00:49:28 +00001820 this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07001821
bsalomon4b4a7cc2016-07-08 04:42:54 -07001822 // Outset bounds to include half-pixel width antialiasing.
Greg Daniel2655ede2019-04-10 00:49:28 +00001823 fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001824
Brian Salomon05441c42017-05-15 16:45:49 -04001825 fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0;
1826 fViewMatrixIfUsingLocalCoords = viewMatrix;
bsalomoncdaa97b2016-03-08 08:30:14 -08001827 }
joshualitt76e7fb62015-02-11 08:52:27 -08001828
Brian Salomon289e3d82016-12-14 15:52:56 -05001829 const char* name() const override { return "EllipseOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08001830
Chris Dalton1706cbf2019-05-21 19:35:29 -06001831 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001832 fHelper.visitProxies(func);
1833 }
1834
Brian Osman9a390ac2018-11-12 09:47:48 -05001835#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05001836 SkString dumpInfo() const override {
1837 SkString string;
1838 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04001839 for (const auto& geo : fEllipses) {
Brian Salomon289e3d82016-12-14 15:52:56 -05001840 string.appendf(
1841 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
1842 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04001843 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04001844 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
1845 geo.fInnerXRadius, geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05001846 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04001847 string += fHelper.dumpInfo();
1848 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05001849 return string;
1850 }
Brian Osman9a390ac2018-11-12 09:47:48 -05001851#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05001852
Chris Dalton6ce447a2019-06-23 18:07:38 -06001853 GrProcessorSet::Analysis finalize(
1854 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
1855 GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001856 fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
1857 !caps.shaderCaps()->hasLowFragmentPrecision();
Brian Osmancf860852018-10-31 14:04:39 -04001858 SkPMColor4f* color = &fEllipses.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06001859 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04001860 GrProcessorAnalysisCoverage::kSingleChannel, color,
1861 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001862 }
1863
1864 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1865
bsalomone46f9fe2015-08-18 06:05:14 -07001866private:
Brian Salomon91326c32017-08-09 16:02:19 -04001867 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08001868 SkMatrix localMatrix;
1869 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001870 return;
1871 }
1872
1873 // Setup geometry processor
Greg Daniel2655ede2019-04-10 00:49:28 +00001874 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
1875 localMatrix));
Brian Osman9d958b52018-11-13 12:46:56 -05001876 QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
Brian Osmanf9aabff2018-11-13 16:11:38 -05001877 GrVertexWriter verts{helper.vertices()};
1878 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08001879 return;
1880 }
1881
Brian Salomon05441c42017-05-15 16:45:49 -04001882 for (const auto& ellipse : fEllipses) {
Brian Osmane3caf2d2018-11-21 13:48:36 -05001883 GrVertexColor color(ellipse.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001884 SkScalar xRadius = ellipse.fXRadius;
1885 SkScalar yRadius = ellipse.fYRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001886
1887 // Compute the reciprocals of the radii here to save time in the shader
Brian Osman9d958b52018-11-13 12:46:56 -05001888 struct { float xOuter, yOuter, xInner, yInner; } invRadii = {
1889 SkScalarInvert(xRadius),
1890 SkScalarInvert(yRadius),
1891 SkScalarInvert(ellipse.fInnerXRadius),
1892 SkScalarInvert(ellipse.fInnerYRadius)
1893 };
Greg Daniel2655ede2019-04-10 00:49:28 +00001894 SkScalar xMaxOffset = xRadius + SK_ScalarHalf;
1895 SkScalar yMaxOffset = yRadius + SK_ScalarHalf;
vjiaoblack977996d2016-06-30 12:20:54 -07001896
Jim Van Verthb1b87d92018-06-28 10:46:05 -04001897 if (!fStroked) {
1898 // For filled ellipses we map a unit circle in the vertex attributes rather than
1899 // computing an ellipse and modifying that distance, so we normalize to 1
1900 xMaxOffset /= xRadius;
1901 yMaxOffset /= yRadius;
1902 }
1903
joshualitt76e7fb62015-02-11 08:52:27 -08001904 // The inner radius in the vertex data must be specified in normalized space.
Brian Osman2b6e3902018-11-21 15:29:43 -05001905 verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fDevBounds),
1906 color,
1907 origin_centered_tri_strip(xMaxOffset, yMaxOffset),
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001908 GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)),
Brian Osman2b6e3902018-11-21 15:29:43 -05001909 invRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08001910 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07001911 helper.recordDraw(target, std::move(gp));
1912 }
1913
1914 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1915 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08001916 }
1917
Brian Salomon7eae3e02018-08-07 14:02:38 +00001918 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05001919 EllipseOp* that = t->cast<EllipseOp>();
bsalomonabd30f52015-08-13 13:34:48 -07001920
Brian Salomon05441c42017-05-15 16:45:49 -04001921 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001922 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07001923 }
1924
bsalomoncdaa97b2016-03-08 08:30:14 -08001925 if (fStroked != that->fStroked) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001926 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08001927 }
1928
Brian Salomon05441c42017-05-15 16:45:49 -04001929 if (fHelper.usesLocalCoords() &&
1930 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001931 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08001932 }
1933
Brian Salomon05441c42017-05-15 16:45:49 -04001934 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
Brian Osmane3caf2d2018-11-21 13:48:36 -05001935 fWideColor |= that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00001936 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08001937 }
1938
Brian Salomon05441c42017-05-15 16:45:49 -04001939 struct Ellipse {
Brian Osmancf860852018-10-31 14:04:39 -04001940 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001941 SkScalar fXRadius;
1942 SkScalar fYRadius;
1943 SkScalar fInnerXRadius;
1944 SkScalar fInnerYRadius;
1945 SkRect fDevBounds;
1946 };
joshualitt76e7fb62015-02-11 08:52:27 -08001947
Brian Salomon289e3d82016-12-14 15:52:56 -05001948 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04001949 Helper fHelper;
1950 bool fStroked;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001951 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001952 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04001953 SkSTArray<1, Ellipse, true> fEllipses;
reed1b55a962015-09-17 20:16:13 -07001954
Brian Salomon05441c42017-05-15 16:45:49 -04001955 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08001956};
1957
joshualitt76e7fb62015-02-11 08:52:27 -08001958/////////////////////////////////////////////////////////////////////////////////////////////////
1959
Brian Salomon05441c42017-05-15 16:45:49 -04001960class DIEllipseOp : public GrMeshDrawOp {
1961private:
1962 using Helper = GrSimpleMeshDrawOpHelper;
1963
1964 struct DeviceSpaceParams {
1965 SkPoint fCenter;
1966 SkScalar fXRadius;
1967 SkScalar fYRadius;
1968 SkScalar fInnerXRadius;
1969 SkScalar fInnerYRadius;
1970 DIEllipseStyle fStyle;
1971 };
1972
joshualitt76e7fb62015-02-11 08:52:27 -08001973public:
Brian Salomon25a88092016-12-01 09:36:50 -05001974 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07001975
Robert Phillipsb97da532019-02-12 15:24:12 -05001976 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04001977 GrPaint&& paint,
1978 const SkMatrix& viewMatrix,
1979 const SkRect& ellipse,
1980 const SkStrokeRec& stroke) {
Brian Salomon05441c42017-05-15 16:45:49 -04001981 DeviceSpaceParams params;
1982 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
1983 params.fXRadius = SkScalarHalf(ellipse.width());
1984 params.fYRadius = SkScalarHalf(ellipse.height());
joshualitt76e7fb62015-02-11 08:52:27 -08001985
bsalomon4b4a7cc2016-07-08 04:42:54 -07001986 SkStrokeRec::Style style = stroke.getStyle();
Brian Salomon05441c42017-05-15 16:45:49 -04001987 params.fStyle = (SkStrokeRec::kStroke_Style == style)
1988 ? DIEllipseStyle::kStroke
1989 : (SkStrokeRec::kHairline_Style == style)
1990 ? DIEllipseStyle::kHairline
1991 : DIEllipseStyle::kFill;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001992
Brian Salomon05441c42017-05-15 16:45:49 -04001993 params.fInnerXRadius = 0;
1994 params.fInnerYRadius = 0;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001995 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) {
1996 SkScalar strokeWidth = stroke.getWidth();
1997
1998 if (SkScalarNearlyZero(strokeWidth)) {
1999 strokeWidth = SK_ScalarHalf;
2000 } else {
2001 strokeWidth *= SK_ScalarHalf;
2002 }
2003
2004 // we only handle thick strokes for near-circular ellipses
2005 if (strokeWidth > SK_ScalarHalf &&
Brian Salomon05441c42017-05-15 16:45:49 -04002006 (SK_ScalarHalf * params.fXRadius > params.fYRadius ||
2007 SK_ScalarHalf * params.fYRadius > params.fXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002008 return nullptr;
2009 }
2010
2011 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon05441c42017-05-15 16:45:49 -04002012 if (strokeWidth * (params.fYRadius * params.fYRadius) <
2013 (strokeWidth * strokeWidth) * params.fXRadius) {
2014 return nullptr;
2015 }
2016 if (strokeWidth * (params.fXRadius * params.fXRadius) <
2017 (strokeWidth * strokeWidth) * params.fYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002018 return nullptr;
2019 }
2020
2021 // set inner radius (if needed)
2022 if (SkStrokeRec::kStroke_Style == style) {
Brian Salomon05441c42017-05-15 16:45:49 -04002023 params.fInnerXRadius = params.fXRadius - strokeWidth;
2024 params.fInnerYRadius = params.fYRadius - strokeWidth;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002025 }
2026
Brian Salomon05441c42017-05-15 16:45:49 -04002027 params.fXRadius += strokeWidth;
2028 params.fYRadius += strokeWidth;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002029 }
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002030
2031 // For large ovals with low precision floats, we fall back to the path renderer.
2032 // To compute the AA at the edge we divide by the gradient, which is clamped to a
2033 // minimum value to avoid divides by zero. With large ovals and low precision this
2034 // leads to blurring at the edge of the oval.
2035 const SkScalar kMaxOvalRadius = 16384;
2036 if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
2037 (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
2038 return nullptr;
2039 }
2040
Brian Salomon05441c42017-05-15 16:45:49 -04002041 if (DIEllipseStyle::kStroke == params.fStyle &&
2042 (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) {
2043 params.fStyle = DIEllipseStyle::kFill;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002044 }
Greg Daniel2655ede2019-04-10 00:49:28 +00002045 return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), params, viewMatrix);
Brian Salomon05441c42017-05-15 16:45:49 -04002046 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002047
Greg Daniel2655ede2019-04-10 00:49:28 +00002048 DIEllipseOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002049 const DeviceSpaceParams& params, const SkMatrix& viewMatrix)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002050 : INHERITED(ClassID())
2051 , fHelper(helperArgs, GrAAType::kCoverage)
2052 , fUseScale(false) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002053 // This expands the outer rect so that after CTM we end up with a half-pixel border
2054 SkScalar a = viewMatrix[SkMatrix::kMScaleX];
2055 SkScalar b = viewMatrix[SkMatrix::kMSkewX];
2056 SkScalar c = viewMatrix[SkMatrix::kMSkewY];
2057 SkScalar d = viewMatrix[SkMatrix::kMScaleY];
2058 SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c);
2059 SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002060
Brian Salomon05441c42017-05-15 16:45:49 -04002061 fEllipses.emplace_back(
2062 Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius,
2063 params.fInnerYRadius, geoDx, geoDy, params.fStyle,
2064 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius - geoDx,
2065 params.fCenter.fY - params.fYRadius - geoDy,
2066 params.fCenter.fX + params.fXRadius + geoDx,
2067 params.fCenter.fY + params.fYRadius + geoDy)});
Greg Daniel2655ede2019-04-10 00:49:28 +00002068 this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes,
Brian Salomon05441c42017-05-15 16:45:49 -04002069 IsZeroArea::kNo);
joshualitt76e7fb62015-02-11 08:52:27 -08002070 }
2071
Brian Salomon289e3d82016-12-14 15:52:56 -05002072 const char* name() const override { return "DIEllipseOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002073
Chris Dalton1706cbf2019-05-21 19:35:29 -06002074 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002075 fHelper.visitProxies(func);
2076 }
2077
Brian Osman9a390ac2018-11-12 09:47:48 -05002078#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05002079 SkString dumpInfo() const override {
2080 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002081 for (const auto& geo : fEllipses) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002082 string.appendf(
2083 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, "
2084 "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, "
2085 "GeoDY: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002086 geo.fColor.toBytes_RGBA(), geo.fBounds.fLeft, geo.fBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002087 geo.fBounds.fRight, geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius,
2088 geo.fInnerXRadius, geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002089 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002090 string += fHelper.dumpInfo();
2091 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002092 return string;
2093 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002094#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05002095
Chris Dalton6ce447a2019-06-23 18:07:38 -06002096 GrProcessorSet::Analysis finalize(
2097 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
2098 GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002099 fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
2100 !caps.shaderCaps()->hasLowFragmentPrecision();
Brian Osmancf860852018-10-31 14:04:39 -04002101 SkPMColor4f* color = &fEllipses.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06002102 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002103 GrProcessorAnalysisCoverage::kSingleChannel, color,
2104 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002105 }
2106
2107 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2108
bsalomone46f9fe2015-08-18 06:05:14 -07002109private:
Brian Salomon91326c32017-08-09 16:02:19 -04002110 void onPrepareDraws(Target* target) override {
joshualitt76e7fb62015-02-11 08:52:27 -08002111 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002112 sk_sp<GrGeometryProcessor> gp(
Greg Daniel2655ede2019-04-10 00:49:28 +00002113 new DIEllipseGeometryProcessor(fWideColor, fUseScale, this->viewMatrix(),
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002114 this->style()));
joshualitt76e7fb62015-02-11 08:52:27 -08002115
Brian Osman9d958b52018-11-13 12:46:56 -05002116 QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
Brian Osmanf9aabff2018-11-13 16:11:38 -05002117 GrVertexWriter verts{helper.vertices()};
2118 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002119 return;
2120 }
2121
Brian Salomon05441c42017-05-15 16:45:49 -04002122 for (const auto& ellipse : fEllipses) {
Brian Osmane3caf2d2018-11-21 13:48:36 -05002123 GrVertexColor color(ellipse.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002124 SkScalar xRadius = ellipse.fXRadius;
2125 SkScalar yRadius = ellipse.fYRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002126
joshualitt76e7fb62015-02-11 08:52:27 -08002127 // This adjusts the "radius" to include the half-pixel border
Brian Salomon05441c42017-05-15 16:45:49 -04002128 SkScalar offsetDx = ellipse.fGeoDx / xRadius;
2129 SkScalar offsetDy = ellipse.fGeoDy / yRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002130
Brian Osman9d958b52018-11-13 12:46:56 -05002131 // By default, constructed so that inner offset is (0, 0) for all points
2132 SkScalar innerRatioX = -offsetDx;
2133 SkScalar innerRatioY = -offsetDy;
joshualitt76e7fb62015-02-11 08:52:27 -08002134
Brian Osman9d958b52018-11-13 12:46:56 -05002135 // ... unless we're stroked
Greg Daniel75a13022018-04-04 08:59:20 -04002136 if (DIEllipseStyle::kStroke == this->style()) {
Brian Osman9d958b52018-11-13 12:46:56 -05002137 innerRatioX = xRadius / ellipse.fInnerXRadius;
2138 innerRatioY = yRadius / ellipse.fInnerYRadius;
Greg Daniel75a13022018-04-04 08:59:20 -04002139 }
joshualitt76e7fb62015-02-11 08:52:27 -08002140
Brian Osman2b6e3902018-11-21 15:29:43 -05002141 verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fBounds),
2142 color,
2143 origin_centered_tri_strip(1.0f + offsetDx, 1.0f + offsetDy),
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002144 GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)),
Brian Osman2b6e3902018-11-21 15:29:43 -05002145 origin_centered_tri_strip(innerRatioX + offsetDx,
2146 innerRatioY + offsetDy));
joshualitt76e7fb62015-02-11 08:52:27 -08002147 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002148 helper.recordDraw(target, std::move(gp));
2149 }
2150
2151 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2152 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002153 }
halcanary9d524f22016-03-29 09:03:52 -07002154
Brian Salomon7eae3e02018-08-07 14:02:38 +00002155 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002156 DIEllipseOp* that = t->cast<DIEllipseOp>();
Brian Salomon05441c42017-05-15 16:45:49 -04002157 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002158 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002159 }
2160
bsalomoncdaa97b2016-03-08 08:30:14 -08002161 if (this->style() != that->style()) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002162 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002163 }
2164
joshualittd96a67b2015-05-05 14:09:05 -07002165 // TODO rewrite to allow positioning on CPU
2166 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002167 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002168 }
2169
Brian Salomon05441c42017-05-15 16:45:49 -04002170 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
Brian Osmane3caf2d2018-11-21 13:48:36 -05002171 fWideColor |= that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002172 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002173 }
2174
Brian Salomon05441c42017-05-15 16:45:49 -04002175 const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; }
2176 DIEllipseStyle style() const { return fEllipses[0].fStyle; }
joshualitt76e7fb62015-02-11 08:52:27 -08002177
Brian Salomon05441c42017-05-15 16:45:49 -04002178 struct Ellipse {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002179 SkMatrix fViewMatrix;
Brian Osmancf860852018-10-31 14:04:39 -04002180 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002181 SkScalar fXRadius;
2182 SkScalar fYRadius;
2183 SkScalar fInnerXRadius;
2184 SkScalar fInnerYRadius;
2185 SkScalar fGeoDx;
2186 SkScalar fGeoDy;
2187 DIEllipseStyle fStyle;
2188 SkRect fBounds;
2189 };
2190
Brian Salomon05441c42017-05-15 16:45:49 -04002191 Helper fHelper;
Brian Osmane3caf2d2018-11-21 13:48:36 -05002192 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002193 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04002194 SkSTArray<1, Ellipse, true> fEllipses;
reed1b55a962015-09-17 20:16:13 -07002195
Brian Salomon05441c42017-05-15 16:45:49 -04002196 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002197};
2198
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002199///////////////////////////////////////////////////////////////////////////////
2200
Mike Kleinfc6c37b2016-09-27 09:34:10 -04002201// We have three possible cases for geometry for a roundrect.
jvanverthc3d0e422016-08-25 08:12:35 -07002202//
2203// In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch:
2204// ____________
2205// |_|________|_|
2206// | | | |
2207// | | | |
2208// | | | |
2209// |_|________|_|
2210// |_|________|_|
2211//
2212// For strokes, we don't draw the center quad.
2213//
2214// For circular roundrects, in the case where the stroke width is greater than twice
2215// the corner radius (overstroke), we add additional geometry to mark out the rectangle
jvanverth6a397612016-08-26 08:15:33 -07002216// in the center. The shared vertices are duplicated so we can set a different outer radius
2217// for the fill calculation.
jvanverthc3d0e422016-08-25 08:12:35 -07002218// ____________
2219// |_|________|_|
2220// | |\ ____ /| |
2221// | | | | | |
2222// | | |____| | |
2223// |_|/______\|_|
2224// |_|________|_|
2225//
2226// We don't draw the center quad from the fill rect in this case.
Robert Phillips79839d42016-10-06 15:03:34 -04002227//
2228// For filled rrects that need to provide a distance vector we resuse the overstroke
2229// geometry but make the inner rect degenerate (either a point or a horizontal or
2230// vertical line).
jvanverthc3d0e422016-08-25 08:12:35 -07002231
jvanverth84839f62016-08-29 10:16:40 -07002232static const uint16_t gOverstrokeRRectIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -05002233 // clang-format off
2234 // overstroke quads
2235 // we place this at the beginning so that we can skip these indices when rendering normally
2236 16, 17, 19, 16, 19, 18,
2237 19, 17, 23, 19, 23, 21,
2238 21, 23, 22, 21, 22, 20,
2239 22, 16, 18, 22, 18, 20,
jvanverthc3d0e422016-08-25 08:12:35 -07002240
Brian Salomon289e3d82016-12-14 15:52:56 -05002241 // corners
2242 0, 1, 5, 0, 5, 4,
2243 2, 3, 7, 2, 7, 6,
2244 8, 9, 13, 8, 13, 12,
2245 10, 11, 15, 10, 15, 14,
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +00002246
Brian Salomon289e3d82016-12-14 15:52:56 -05002247 // edges
2248 1, 2, 6, 1, 6, 5,
2249 4, 5, 9, 4, 9, 8,
2250 6, 7, 11, 6, 11, 10,
2251 9, 10, 14, 9, 14, 13,
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +00002252
Brian Salomon289e3d82016-12-14 15:52:56 -05002253 // center
2254 // we place this at the end so that we can ignore these indices when not rendering as filled
2255 5, 6, 10, 5, 10, 9,
2256 // clang-format on
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002257};
Brian Salomon289e3d82016-12-14 15:52:56 -05002258
jvanverth84839f62016-08-29 10:16:40 -07002259// fill and standard stroke indices skip the overstroke "ring"
Brian Salomon289e3d82016-12-14 15:52:56 -05002260static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002261
jvanverth84839f62016-08-29 10:16:40 -07002262// overstroke count is arraysize minus the center indices
2263static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6;
2264// fill count skips overstroke indices and includes center
Brian Salomon289e3d82016-12-14 15:52:56 -05002265static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
jvanverth84839f62016-08-29 10:16:40 -07002266// stroke count is fill count minus center indices
jvanverthc3d0e422016-08-25 08:12:35 -07002267static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6;
2268static const int kVertsPerStandardRRect = 16;
jvanverth6a397612016-08-26 08:15:33 -07002269static const int kVertsPerOverstrokeRRect = 24;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002270
jvanverthc3d0e422016-08-25 08:12:35 -07002271enum RRectType {
2272 kFill_RRectType,
2273 kStroke_RRectType,
jvanverth84839f62016-08-29 10:16:40 -07002274 kOverstroke_RRectType,
jvanverthc3d0e422016-08-25 08:12:35 -07002275};
2276
jvanverth84839f62016-08-29 10:16:40 -07002277static int rrect_type_to_vert_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002278 switch (type) {
2279 case kFill_RRectType:
2280 case kStroke_RRectType:
2281 return kVertsPerStandardRRect;
2282 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002283 return kVertsPerOverstrokeRRect;
2284 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002285 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002286 return 0;
jvanverth84839f62016-08-29 10:16:40 -07002287}
2288
2289static int rrect_type_to_index_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002290 switch (type) {
2291 case kFill_RRectType:
2292 return kIndicesPerFillRRect;
2293 case kStroke_RRectType:
2294 return kIndicesPerStrokeRRect;
2295 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002296 return kIndicesPerOverstrokeRRect;
2297 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002298 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002299 return 0;
jvanverth84839f62016-08-29 10:16:40 -07002300}
2301
2302static const uint16_t* rrect_type_to_indices(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002303 switch (type) {
2304 case kFill_RRectType:
2305 case kStroke_RRectType:
2306 return gStandardRRectIndices;
2307 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002308 return gOverstrokeRRectIndices;
2309 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002310 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002311 return 0;
bsalomoned0bcad2015-05-04 10:36:42 -07002312}
2313
joshualitt76e7fb62015-02-11 08:52:27 -08002314///////////////////////////////////////////////////////////////////////////////////////////////////
2315
Robert Phillips79839d42016-10-06 15:03:34 -04002316// For distance computations in the interior of filled rrects we:
2317//
2318// add a interior degenerate (point or line) rect
2319// each vertex of that rect gets -outerRad as its radius
2320// this makes the computation of the distance to the outer edge be negative
2321// negative values are caught and then handled differently in the GP's onEmitCode
2322// each vertex is also given the normalized x & y distance from the interior rect's edge
2323// the GP takes the min of those depths +1 to get the normalized distance to the outer edge
2324
Brian Salomon05441c42017-05-15 16:45:49 -04002325class CircularRRectOp : public GrMeshDrawOp {
2326private:
2327 using Helper = GrSimpleMeshDrawOpHelper;
2328
joshualitt76e7fb62015-02-11 08:52:27 -08002329public:
Brian Salomon25a88092016-12-01 09:36:50 -05002330 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002331
bsalomon4b4a7cc2016-07-08 04:42:54 -07002332 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
2333 // whether the rrect is only stroked or stroked and filled.
Robert Phillipsb97da532019-02-12 15:24:12 -05002334 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002335 GrPaint&& paint,
2336 const SkMatrix& viewMatrix,
2337 const SkRect& devRect,
2338 float devRadius,
2339 float devStrokeWidth,
2340 bool strokeOnly) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002341 return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04002342 devRect, devRadius,
2343 devStrokeWidth, strokeOnly);
Brian Salomon05441c42017-05-15 16:45:49 -04002344 }
Greg Daniel2655ede2019-04-10 00:49:28 +00002345 CircularRRectOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002346 const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius,
2347 float devStrokeWidth, bool strokeOnly)
Brian Salomon05441c42017-05-15 16:45:49 -04002348 : INHERITED(ClassID())
2349 , fViewMatrixIfUsingLocalCoords(viewMatrix)
Greg Daniel2655ede2019-04-10 00:49:28 +00002350 , fHelper(helperArgs, GrAAType::kCoverage) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002351 SkRect bounds = devRect;
2352 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
2353 SkScalar innerRadius = 0.0f;
2354 SkScalar outerRadius = devRadius;
2355 SkScalar halfWidth = 0;
jvanverth84839f62016-08-29 10:16:40 -07002356 RRectType type = kFill_RRectType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002357 if (devStrokeWidth > 0) {
2358 if (SkScalarNearlyZero(devStrokeWidth)) {
2359 halfWidth = SK_ScalarHalf;
2360 } else {
2361 halfWidth = SkScalarHalf(devStrokeWidth);
2362 }
joshualitt76e7fb62015-02-11 08:52:27 -08002363
bsalomon4b4a7cc2016-07-08 04:42:54 -07002364 if (strokeOnly) {
jvanverthc3d0e422016-08-25 08:12:35 -07002365 // Outset stroke by 1/4 pixel
2366 devStrokeWidth += 0.25f;
2367 // If stroke is greater than width or height, this is still a fill
2368 // Otherwise we compute stroke params
Brian Salomon289e3d82016-12-14 15:52:56 -05002369 if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) {
jvanverthc3d0e422016-08-25 08:12:35 -07002370 innerRadius = devRadius - halfWidth;
jvanverth84839f62016-08-29 10:16:40 -07002371 type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
jvanverthc3d0e422016-08-25 08:12:35 -07002372 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002373 }
2374 outerRadius += halfWidth;
2375 bounds.outset(halfWidth, halfWidth);
2376 }
bsalomoncdaa97b2016-03-08 08:30:14 -08002377
Greg Daniel2655ede2019-04-10 00:49:28 +00002378 // The radii are outset for two reasons. First, it allows the shader to simply perform
2379 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
2380 // Second, the outer radius is used to compute the verts of the bounding box that is
2381 // rendered and the outset ensures the box will cover all partially covered by the rrect
2382 // corners.
2383 outerRadius += SK_ScalarHalf;
2384 innerRadius -= SK_ScalarHalf;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002385
Greg Daniel2655ede2019-04-10 00:49:28 +00002386 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07002387
Greg Daniel2655ede2019-04-10 00:49:28 +00002388 // Expand the rect for aa to generate correct vertices.
2389 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002390
Brian Salomon05441c42017-05-15 16:45:49 -04002391 fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type});
jvanverth84839f62016-08-29 10:16:40 -07002392 fVertCount = rrect_type_to_vert_count(type);
2393 fIndexCount = rrect_type_to_index_count(type);
2394 fAllFill = (kFill_RRectType == type);
joshualitt76e7fb62015-02-11 08:52:27 -08002395 }
2396
Brian Salomon289e3d82016-12-14 15:52:56 -05002397 const char* name() const override { return "CircularRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002398
Chris Dalton1706cbf2019-05-21 19:35:29 -06002399 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002400 fHelper.visitProxies(func);
2401 }
2402
Brian Osman9a390ac2018-11-12 09:47:48 -05002403#ifdef SK_DEBUG
jvanverthc3d0e422016-08-25 08:12:35 -07002404 SkString dumpInfo() const override {
2405 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002406 for (int i = 0; i < fRRects.count(); ++i) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002407 string.appendf(
2408 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
2409 "InnerRad: %.2f, OuterRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002410 fRRects[i].fColor.toBytes_RGBA(), fRRects[i].fDevBounds.fLeft,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002411 fRRects[i].fDevBounds.fTop, fRRects[i].fDevBounds.fRight,
2412 fRRects[i].fDevBounds.fBottom, fRRects[i].fInnerRadius,
2413 fRRects[i].fOuterRadius);
jvanverthc3d0e422016-08-25 08:12:35 -07002414 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002415 string += fHelper.dumpInfo();
2416 string += INHERITED::dumpInfo();
jvanverthc3d0e422016-08-25 08:12:35 -07002417 return string;
2418 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002419#endif
jvanverthc3d0e422016-08-25 08:12:35 -07002420
Chris Dalton6ce447a2019-06-23 18:07:38 -06002421 GrProcessorSet::Analysis finalize(
2422 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
2423 GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04002424 SkPMColor4f* color = &fRRects.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06002425 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002426 GrProcessorAnalysisCoverage::kSingleChannel, color,
2427 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002428 }
2429
2430 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2431
Brian Salomon92aee3d2016-12-21 09:20:25 -05002432private:
Brian Osmana1d4eb92018-12-06 16:33:10 -05002433 static void FillInOverstrokeVerts(GrVertexWriter& verts, const SkRect& bounds, SkScalar smInset,
Brian Salomon289e3d82016-12-14 15:52:56 -05002434 SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002435 SkScalar innerRadius, const GrVertexColor& color) {
Robert Phillips79839d42016-10-06 15:03:34 -04002436 SkASSERT(smInset < bigInset);
2437
2438 // TL
Brian Osmana1d4eb92018-12-06 16:33:10 -05002439 verts.write(bounds.fLeft + smInset, bounds.fTop + smInset,
2440 color,
2441 xOffset, 0.0f,
2442 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002443
2444 // TR
Brian Osmana1d4eb92018-12-06 16:33:10 -05002445 verts.write(bounds.fRight - smInset, bounds.fTop + smInset,
2446 color,
2447 xOffset, 0.0f,
2448 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002449
Brian Osmana1d4eb92018-12-06 16:33:10 -05002450 verts.write(bounds.fLeft + bigInset, bounds.fTop + bigInset,
2451 color,
2452 0.0f, 0.0f,
2453 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002454
Brian Osmana1d4eb92018-12-06 16:33:10 -05002455 verts.write(bounds.fRight - bigInset, bounds.fTop + bigInset,
2456 color,
2457 0.0f, 0.0f,
2458 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002459
Brian Osmana1d4eb92018-12-06 16:33:10 -05002460 verts.write(bounds.fLeft + bigInset, bounds.fBottom - bigInset,
2461 color,
2462 0.0f, 0.0f,
2463 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002464
Brian Osmana1d4eb92018-12-06 16:33:10 -05002465 verts.write(bounds.fRight - bigInset, bounds.fBottom - bigInset,
2466 color,
2467 0.0f, 0.0f,
2468 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002469
2470 // BL
Brian Osmana1d4eb92018-12-06 16:33:10 -05002471 verts.write(bounds.fLeft + smInset, bounds.fBottom - smInset,
2472 color,
2473 xOffset, 0.0f,
2474 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002475
2476 // BR
Brian Osmana1d4eb92018-12-06 16:33:10 -05002477 verts.write(bounds.fRight - smInset, bounds.fBottom - smInset,
2478 color,
2479 xOffset, 0.0f,
2480 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002481 }
2482
Brian Salomon91326c32017-08-09 16:02:19 -04002483 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002484 // Invert the view matrix as a local matrix (if any other processors require coords).
2485 SkMatrix localMatrix;
2486 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002487 return;
2488 }
2489
2490 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002491 sk_sp<GrGeometryProcessor> gp(
Greg Daniel2655ede2019-04-10 00:49:28 +00002492 new CircleGeometryProcessor(!fAllFill, false, false, false, false, fWideColor,
2493 localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002494
Brian Salomon12d22642019-01-29 14:38:50 -05002495 sk_sp<const GrBuffer> vertexBuffer;
jvanverth84839f62016-08-29 10:16:40 -07002496 int firstVertex;
joshualitt76e7fb62015-02-11 08:52:27 -08002497
Brian Osmana1d4eb92018-12-06 16:33:10 -05002498 GrVertexWriter verts{target->makeVertexSpace(gp->vertexStride(), fVertCount,
2499 &vertexBuffer, &firstVertex)};
2500 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002501 SkDebugf("Could not allocate vertices\n");
2502 return;
2503 }
2504
Brian Salomon12d22642019-01-29 14:38:50 -05002505 sk_sp<const GrBuffer> indexBuffer;
jvanverth84839f62016-08-29 10:16:40 -07002506 int firstIndex = 0;
2507 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
2508 if (!indices) {
2509 SkDebugf("Could not allocate indices\n");
2510 return;
2511 }
2512
2513 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04002514 for (const auto& rrect : fRRects) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002515 GrVertexColor color(rrect.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002516 SkScalar outerRadius = rrect.fOuterRadius;
2517 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002518
Brian Salomon289e3d82016-12-14 15:52:56 -05002519 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius,
2520 bounds.fBottom - outerRadius, bounds.fBottom};
joshualitt76e7fb62015-02-11 08:52:27 -08002521
Brian Salomon289e3d82016-12-14 15:52:56 -05002522 SkScalar yOuterRadii[4] = {-1, 0, 0, 1};
joshualitt76e7fb62015-02-11 08:52:27 -08002523 // The inner radius in the vertex data must be specified in normalized space.
jvanverth84839f62016-08-29 10:16:40 -07002524 // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
Brian Salomone23bffd2017-06-02 11:01:10 -04002525 SkScalar innerRadius = rrect.fType != kFill_RRectType
2526 ? rrect.fInnerRadius / rrect.fOuterRadius
2527 : -1.0f / rrect.fOuterRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002528 for (int i = 0; i < 4; ++i) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002529 verts.write(bounds.fLeft, yCoords[i],
2530 color,
2531 -1.0f, yOuterRadii[i],
2532 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002533
Brian Osmana1d4eb92018-12-06 16:33:10 -05002534 verts.write(bounds.fLeft + outerRadius, yCoords[i],
2535 color,
2536 0.0f, yOuterRadii[i],
2537 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002538
Brian Osmana1d4eb92018-12-06 16:33:10 -05002539 verts.write(bounds.fRight - outerRadius, yCoords[i],
2540 color,
2541 0.0f, yOuterRadii[i],
2542 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002543
Brian Osmana1d4eb92018-12-06 16:33:10 -05002544 verts.write(bounds.fRight, yCoords[i],
2545 color,
2546 1.0f, yOuterRadii[i],
2547 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002548 }
jvanverthc3d0e422016-08-25 08:12:35 -07002549 // Add the additional vertices for overstroked rrects.
jvanvertha4f1af82016-08-29 07:17:47 -07002550 // Effectively this is an additional stroked rrect, with its
2551 // outer radius = outerRadius - innerRadius, and inner radius = 0.
2552 // This will give us correct AA in the center and the correct
2553 // distance to the outer edge.
jvanverthc3d0e422016-08-25 08:12:35 -07002554 //
jvanvertha4f1af82016-08-29 07:17:47 -07002555 // Also, the outer offset is a constant vector pointing to the right, which
2556 // guarantees that the distance value along the outer rectangle is constant.
Brian Salomon05441c42017-05-15 16:45:49 -04002557 if (kOverstroke_RRectType == rrect.fType) {
2558 SkASSERT(rrect.fInnerRadius <= 0.0f);
Robert Phillips79839d42016-10-06 15:03:34 -04002559
Brian Salomon05441c42017-05-15 16:45:49 -04002560 SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002561 // this is the normalized distance from the outer rectangle of this
2562 // geometry to the outer edge
Brian Salomon05441c42017-05-15 16:45:49 -04002563 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002564
Brian Osmana1d4eb92018-12-06 16:33:10 -05002565 FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002566 overstrokeOuterRadius, 0.0f, color);
Robert Phillips79839d42016-10-06 15:03:34 -04002567 }
jvanverth6a397612016-08-26 08:15:33 -07002568
Brian Salomon05441c42017-05-15 16:45:49 -04002569 const uint16_t* primIndices = rrect_type_to_indices(rrect.fType);
2570 const int primIndexCount = rrect_type_to_index_count(rrect.fType);
jvanverth84839f62016-08-29 10:16:40 -07002571 for (int i = 0; i < primIndexCount; ++i) {
2572 *indices++ = primIndices[i] + currStartVertex;
2573 }
2574
Brian Salomon05441c42017-05-15 16:45:49 -04002575 currStartVertex += rrect_type_to_vert_count(rrect.fType);
joshualitt76e7fb62015-02-11 08:52:27 -08002576 }
2577
Brian Salomon7eae3e02018-08-07 14:02:38 +00002578 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05002579 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00002580 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05002581 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002582 target->recordDraw(std::move(gp), mesh);
2583 }
2584
2585 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2586 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002587 }
2588
Brian Salomon7eae3e02018-08-07 14:02:38 +00002589 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002590 CircularRRectOp* that = t->cast<CircularRRectOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05002591
2592 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05002593 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002594 return CombineResult::kCannotCombine;
Jim Van Verth8cefe402017-02-09 11:36:37 -05002595 }
2596
Brian Salomon05441c42017-05-15 16:45:49 -04002597 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002598 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002599 }
2600
Brian Salomon05441c42017-05-15 16:45:49 -04002601 if (fHelper.usesLocalCoords() &&
2602 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002603 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002604 }
2605
Brian Salomon05441c42017-05-15 16:45:49 -04002606 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
jvanverth84839f62016-08-29 10:16:40 -07002607 fVertCount += that->fVertCount;
2608 fIndexCount += that->fIndexCount;
2609 fAllFill = fAllFill && that->fAllFill;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002610 fWideColor = fWideColor || that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002611 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002612 }
2613
Brian Salomon05441c42017-05-15 16:45:49 -04002614 struct RRect {
Brian Osmancf860852018-10-31 14:04:39 -04002615 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002616 SkScalar fInnerRadius;
2617 SkScalar fOuterRadius;
2618 SkRect fDevBounds;
jvanverth84839f62016-08-29 10:16:40 -07002619 RRectType fType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002620 };
2621
Brian Salomon289e3d82016-12-14 15:52:56 -05002622 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002623 Helper fHelper;
Brian Salomon289e3d82016-12-14 15:52:56 -05002624 int fVertCount;
2625 int fIndexCount;
2626 bool fAllFill;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002627 bool fWideColor;
Brian Salomon05441c42017-05-15 16:45:49 -04002628 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002629
Brian Salomon05441c42017-05-15 16:45:49 -04002630 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002631};
2632
jvanverth84839f62016-08-29 10:16:40 -07002633static const int kNumRRectsInIndexBuffer = 256;
2634
2635GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2636GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
Brian Salomond28a79d2017-10-16 13:01:07 -04002637static sk_sp<const GrBuffer> get_rrect_index_buffer(RRectType type,
2638 GrResourceProvider* resourceProvider) {
jvanverth84839f62016-08-29 10:16:40 -07002639 GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2640 GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
2641 switch (type) {
2642 case kFill_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002643 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002644 gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer,
2645 kVertsPerStandardRRect, gRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002646 case kStroke_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002647 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002648 gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer,
2649 kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002650 default:
2651 SkASSERT(false);
2652 return nullptr;
Brian Salomon23356442018-11-30 15:33:19 -05002653 }
jvanverth84839f62016-08-29 10:16:40 -07002654}
2655
Brian Salomon05441c42017-05-15 16:45:49 -04002656class EllipticalRRectOp : public GrMeshDrawOp {
2657private:
2658 using Helper = GrSimpleMeshDrawOpHelper;
2659
joshualitt76e7fb62015-02-11 08:52:27 -08002660public:
Brian Salomon25a88092016-12-01 09:36:50 -05002661 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002662
bsalomon4b4a7cc2016-07-08 04:42:54 -07002663 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
2664 // whether the rrect is only stroked or stroked and filled.
Robert Phillipsb97da532019-02-12 15:24:12 -05002665 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002666 GrPaint&& paint,
2667 const SkMatrix& viewMatrix,
2668 const SkRect& devRect,
2669 float devXRadius,
2670 float devYRadius,
2671 SkVector devStrokeWidths,
2672 bool strokeOnly) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002673 SkASSERT(devXRadius > 0.5);
2674 SkASSERT(devYRadius > 0.5);
2675 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0));
2676 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0));
bsalomon4b4a7cc2016-07-08 04:42:54 -07002677 if (devStrokeWidths.fX > 0) {
2678 if (SkScalarNearlyZero(devStrokeWidths.length())) {
2679 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf);
2680 } else {
2681 devStrokeWidths.scale(SK_ScalarHalf);
2682 }
joshualitt76e7fb62015-02-11 08:52:27 -08002683
bsalomon4b4a7cc2016-07-08 04:42:54 -07002684 // we only handle thick strokes for near-circular ellipses
2685 if (devStrokeWidths.length() > SK_ScalarHalf &&
Brian Salomon289e3d82016-12-14 15:52:56 -05002686 (SK_ScalarHalf * devXRadius > devYRadius ||
2687 SK_ScalarHalf * devYRadius > devXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002688 return nullptr;
2689 }
2690
2691 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon289e3d82016-12-14 15:52:56 -05002692 if (devStrokeWidths.fX * (devYRadius * devYRadius) <
2693 (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002694 return nullptr;
2695 }
Brian Salomon289e3d82016-12-14 15:52:56 -05002696 if (devStrokeWidths.fY * (devXRadius * devXRadius) <
2697 (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002698 return nullptr;
2699 }
Brian Salomon05441c42017-05-15 16:45:49 -04002700 }
Robert Phillips7c525e62018-06-12 10:11:12 -04002701 return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint),
Greg Daniel2655ede2019-04-10 00:49:28 +00002702 viewMatrix, devRect,
Brian Salomon05441c42017-05-15 16:45:49 -04002703 devXRadius, devYRadius, devStrokeWidths,
2704 strokeOnly);
2705 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002706
Greg Daniel2655ede2019-04-10 00:49:28 +00002707 EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002708 const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius,
2709 float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002710 : INHERITED(ClassID())
2711 , fHelper(helperArgs, GrAAType::kCoverage)
2712 , fUseScale(false) {
Brian Salomon05441c42017-05-15 16:45:49 -04002713 SkScalar innerXRadius = 0.0f;
2714 SkScalar innerYRadius = 0.0f;
2715 SkRect bounds = devRect;
2716 bool stroked = false;
2717 if (devStrokeHalfWidths.fX > 0) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002718 // this is legit only if scale & translation (which should be the case at the moment)
2719 if (strokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04002720 innerXRadius = devXRadius - devStrokeHalfWidths.fX;
2721 innerYRadius = devYRadius - devStrokeHalfWidths.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002722 stroked = (innerXRadius >= 0 && innerYRadius >= 0);
2723 }
2724
Brian Salomon05441c42017-05-15 16:45:49 -04002725 devXRadius += devStrokeHalfWidths.fX;
2726 devYRadius += devStrokeHalfWidths.fY;
2727 bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002728 }
2729
Brian Salomon05441c42017-05-15 16:45:49 -04002730 fStroked = stroked;
2731 fViewMatrixIfUsingLocalCoords = viewMatrix;
Greg Daniel2655ede2019-04-10 00:49:28 +00002732 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
2733 // Expand the rect for aa in order to generate the correct vertices.
2734 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
Brian Salomon05441c42017-05-15 16:45:49 -04002735 fRRects.emplace_back(
2736 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
joshualitt76e7fb62015-02-11 08:52:27 -08002737 }
2738
Brian Salomon289e3d82016-12-14 15:52:56 -05002739 const char* name() const override { return "EllipticalRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002740
Chris Dalton1706cbf2019-05-21 19:35:29 -06002741 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002742 fHelper.visitProxies(func);
2743 }
2744
Brian Osman9a390ac2018-11-12 09:47:48 -05002745#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05002746 SkString dumpInfo() const override {
2747 SkString string;
2748 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04002749 for (const auto& geo : fRRects) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002750 string.appendf(
2751 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
2752 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002753 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002754 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
2755 geo.fInnerXRadius, geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002756 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002757 string += fHelper.dumpInfo();
2758 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002759 return string;
2760 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002761#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05002762
Chris Dalton6ce447a2019-06-23 18:07:38 -06002763 GrProcessorSet::Analysis finalize(
2764 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
2765 GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002766 fUseScale = !caps.shaderCaps()->floatIs32Bits();
Brian Osmancf860852018-10-31 14:04:39 -04002767 SkPMColor4f* color = &fRRects.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06002768 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002769 GrProcessorAnalysisCoverage::kSingleChannel, color,
2770 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002771 }
2772
2773 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2774
bsalomone46f9fe2015-08-18 06:05:14 -07002775private:
Brian Salomon91326c32017-08-09 16:02:19 -04002776 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002777 SkMatrix localMatrix;
2778 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002779 return;
2780 }
2781
2782 // Setup geometry processor
Greg Daniel2655ede2019-04-10 00:49:28 +00002783 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002784 localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002785
bsalomonb5238a72015-05-05 07:49:49 -07002786 // drop out the middle quad if we're stroked
jvanverthc3d0e422016-08-25 08:12:35 -07002787 int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
Brian Salomond28a79d2017-10-16 13:01:07 -04002788 sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer(
2789 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider());
joshualitt76e7fb62015-02-11 08:52:27 -08002790
Brian Salomon12d22642019-01-29 14:38:50 -05002791 if (!indexBuffer) {
2792 SkDebugf("Could not allocate indices\n");
2793 return;
2794 }
Brian Osmana1d4eb92018-12-06 16:33:10 -05002795 PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(),
Brian Salomon12d22642019-01-29 14:38:50 -05002796 std::move(indexBuffer), kVertsPerStandardRRect, indicesPerInstance,
Brian Salomon7eae3e02018-08-07 14:02:38 +00002797 fRRects.count());
Brian Osmana1d4eb92018-12-06 16:33:10 -05002798 GrVertexWriter verts{helper.vertices()};
Brian Salomon12d22642019-01-29 14:38:50 -05002799 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002800 SkDebugf("Could not allocate vertices\n");
2801 return;
2802 }
2803
Brian Salomon05441c42017-05-15 16:45:49 -04002804 for (const auto& rrect : fRRects) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002805 GrVertexColor color(rrect.fColor, fWideColor);
joshualitt76e7fb62015-02-11 08:52:27 -08002806 // Compute the reciprocals of the radii here to save time in the shader
Brian Osmana1d4eb92018-12-06 16:33:10 -05002807 float reciprocalRadii[4] = {
2808 SkScalarInvert(rrect.fXRadius),
2809 SkScalarInvert(rrect.fYRadius),
2810 SkScalarInvert(rrect.fInnerXRadius),
2811 SkScalarInvert(rrect.fInnerYRadius)
2812 };
joshualitt76e7fb62015-02-11 08:52:27 -08002813
2814 // Extend the radii out half a pixel to antialias.
Greg Daniel2655ede2019-04-10 00:49:28 +00002815 SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf;
2816 SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf;
joshualitt76e7fb62015-02-11 08:52:27 -08002817
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002818 SkScalar xMaxOffset = xOuterRadius;
2819 SkScalar yMaxOffset = yOuterRadius;
2820 if (!fStroked) {
2821 // For filled rrects we map a unit circle in the vertex attributes rather than
2822 // computing an ellipse and modifying that distance, so we normalize to 1.
2823 xMaxOffset /= rrect.fXRadius;
2824 yMaxOffset /= rrect.fYRadius;
2825 }
2826
Brian Salomon05441c42017-05-15 16:45:49 -04002827 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002828
Brian Salomon289e3d82016-12-14 15:52:56 -05002829 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius,
2830 bounds.fBottom - yOuterRadius, bounds.fBottom};
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002831 SkScalar yOuterOffsets[4] = {yMaxOffset,
Brian Salomon289e3d82016-12-14 15:52:56 -05002832 SK_ScalarNearlyZero, // we're using inversesqrt() in
2833 // shader, so can't be exactly 0
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002834 SK_ScalarNearlyZero, yMaxOffset};
joshualitt76e7fb62015-02-11 08:52:27 -08002835
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002836 auto maybeScale = GrVertexWriter::If(fUseScale, SkTMax(rrect.fXRadius, rrect.fYRadius));
joshualitt76e7fb62015-02-11 08:52:27 -08002837 for (int i = 0; i < 4; ++i) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002838 verts.write(bounds.fLeft, yCoords[i],
2839 color,
2840 xMaxOffset, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002841 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002842 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002843
Brian Osmana1d4eb92018-12-06 16:33:10 -05002844 verts.write(bounds.fLeft + xOuterRadius, yCoords[i],
2845 color,
2846 SK_ScalarNearlyZero, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002847 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002848 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002849
Brian Osmana1d4eb92018-12-06 16:33:10 -05002850 verts.write(bounds.fRight - xOuterRadius, yCoords[i],
2851 color,
2852 SK_ScalarNearlyZero, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002853 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002854 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002855
Brian Osmana1d4eb92018-12-06 16:33:10 -05002856 verts.write(bounds.fRight, yCoords[i],
2857 color,
2858 xMaxOffset, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002859 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002860 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002861 }
2862 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002863 helper.recordDraw(target, std::move(gp));
2864 }
2865
2866 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2867 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002868 }
2869
Brian Salomon7eae3e02018-08-07 14:02:38 +00002870 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002871 EllipticalRRectOp* that = t->cast<EllipticalRRectOp>();
bsalomonabd30f52015-08-13 13:34:48 -07002872
Brian Salomon05441c42017-05-15 16:45:49 -04002873 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002874 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002875 }
2876
bsalomoncdaa97b2016-03-08 08:30:14 -08002877 if (fStroked != that->fStroked) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002878 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002879 }
2880
Brian Salomon05441c42017-05-15 16:45:49 -04002881 if (fHelper.usesLocalCoords() &&
2882 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002883 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002884 }
Brian Salomonea26d6b2018-01-23 20:33:21 +00002885
Brian Salomon05441c42017-05-15 16:45:49 -04002886 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
Brian Osmana1d4eb92018-12-06 16:33:10 -05002887 fWideColor = fWideColor || that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002888 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002889 }
2890
Brian Salomon05441c42017-05-15 16:45:49 -04002891 struct RRect {
Brian Osmancf860852018-10-31 14:04:39 -04002892 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002893 SkScalar fXRadius;
2894 SkScalar fYRadius;
2895 SkScalar fInnerXRadius;
2896 SkScalar fInnerYRadius;
2897 SkRect fDevBounds;
2898 };
2899
Brian Salomon289e3d82016-12-14 15:52:56 -05002900 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002901 Helper fHelper;
2902 bool fStroked;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002903 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002904 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04002905 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002906
Brian Salomon05441c42017-05-15 16:45:49 -04002907 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002908};
2909
Jim Van Verth64b85892019-06-17 12:01:46 -04002910std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context,
2911 GrPaint&& paint,
2912 const SkMatrix& viewMatrix,
2913 const SkRRect& rrect,
2914 const SkStrokeRec& stroke,
2915 const GrShaderCaps* shaderCaps) {
2916 SkASSERT(viewMatrix.rectStaysRect());
2917 SkASSERT(viewMatrix.isSimilarity());
2918 SkASSERT(rrect.isSimple());
2919 SkASSERT(!rrect.isOval());
2920 SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY);
2921
2922 // RRect ops only handle simple, but not too simple, rrects.
2923 // Do any matrix crunching before we reset the draw state for device coords.
2924 const SkRect& rrectBounds = rrect.getBounds();
2925 SkRect bounds;
2926 viewMatrix.mapRect(&bounds, rrectBounds);
2927
2928 SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
2929 SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] +
2930 viewMatrix[SkMatrix::kMSkewY]));
2931
2932 // Do mapping of stroke. Use -1 to indicate fill-only draws.
2933 SkScalar scaledStroke = -1;
2934 SkScalar strokeWidth = stroke.getWidth();
2935 SkStrokeRec::Style style = stroke.getStyle();
2936
2937 bool isStrokeOnly =
2938 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
2939 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
2940
2941 if (hasStroke) {
2942 if (SkStrokeRec::kHairline_Style == style) {
2943 scaledStroke = SK_Scalar1;
2944 } else {
2945 scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
2946 }
2947 }
2948
2949 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
2950 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
2951 // patch will have fractional coverage. This only matters when the interior is actually filled.
2952 // We could consider falling back to rect rendering here, since a tiny radius is
2953 // indistinguishable from a square corner.
2954 if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) {
2955 return nullptr;
2956 }
2957
2958 return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius,
2959 scaledStroke, isStrokeOnly);
2960}
2961
Robert Phillipsb97da532019-02-12 15:24:12 -05002962static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002963 GrPaint&& paint,
Brian Salomon05441c42017-05-15 16:45:49 -04002964 const SkMatrix& viewMatrix,
2965 const SkRRect& rrect,
2966 const SkStrokeRec& stroke) {
joshualitt3e708c52015-04-30 13:49:27 -07002967 SkASSERT(viewMatrix.rectStaysRect());
2968 SkASSERT(rrect.isSimple());
2969 SkASSERT(!rrect.isOval());
commit-bot@chromium.orge0a868c2013-11-22 07:02:11 +00002970
Brian Salomon53e4c3c2016-12-21 11:38:53 -05002971 // RRect ops only handle simple, but not too simple, rrects.
2972 // Do any matrix crunching before we reset the draw state for device coords.
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002973 const SkRect& rrectBounds = rrect.getBounds();
joshualittd96a67b2015-05-05 14:09:05 -07002974 SkRect bounds;
2975 viewMatrix.mapRect(&bounds, rrectBounds);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002976
Mike Reed242135a2018-02-22 13:41:39 -05002977 SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
Brian Salomon289e3d82016-12-14 15:52:56 -05002978 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
2979 viewMatrix[SkMatrix::kMSkewY] * radii.fY);
2980 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
2981 viewMatrix[SkMatrix::kMScaleY] * radii.fY);
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +00002982
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002983 SkStrokeRec::Style style = stroke.getStyle();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002984
bsalomon4b4a7cc2016-07-08 04:42:54 -07002985 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
2986 SkVector scaledStroke = {-1, -1};
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002987 SkScalar strokeWidth = stroke.getWidth();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002988
Brian Salomon289e3d82016-12-14 15:52:56 -05002989 bool isStrokeOnly =
2990 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002991 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
2992
2993 if (hasStroke) {
2994 if (SkStrokeRec::kHairline_Style == style) {
2995 scaledStroke.set(1, 1);
2996 } else {
Brian Salomon289e3d82016-12-14 15:52:56 -05002997 scaledStroke.fX = SkScalarAbs(
2998 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
2999 scaledStroke.fY = SkScalarAbs(
3000 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003001 }
3002
Jim Van Verth64b85892019-06-17 12:01:46 -04003003 // if half of strokewidth is greater than radius, we don't handle that right now
3004 if ((SK_ScalarHalf * scaledStroke.fX > xRadius ||
3005 SK_ScalarHalf * scaledStroke.fY > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07003006 return nullptr;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003007 }
3008 }
3009
Brian Salomon8a97f562019-04-18 14:07:27 -04003010 // The matrix may have a rotation by an odd multiple of 90 degrees.
Jim Van Verth64b85892019-06-17 12:01:46 -04003011 if (viewMatrix.getScaleX() == 0) {
Brian Salomon8a97f562019-04-18 14:07:27 -04003012 std::swap(xRadius, yRadius);
3013 std::swap(scaledStroke.fX, scaledStroke.fY);
3014 }
3015
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003016 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
3017 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
3018 // patch will have fractional coverage. This only matters when the interior is actually filled.
3019 // We could consider falling back to rect rendering here, since a tiny radius is
3020 // indistinguishable from a square corner.
3021 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07003022 return nullptr;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003023 }
3024
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003025 // if the corners are circles, use the circle renderer
Jim Van Verth64b85892019-06-17 12:01:46 -04003026 return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
3027 xRadius, yRadius, scaledStroke, isStrokeOnly);
joshualitt3e708c52015-04-30 13:49:27 -07003028}
3029
Robert Phillipsb97da532019-02-12 15:24:12 -05003030std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003031 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003032 const SkMatrix& viewMatrix,
3033 const SkRRect& rrect,
3034 const SkStrokeRec& stroke,
3035 const GrShaderCaps* shaderCaps) {
robertphillips0cc2f852016-02-24 13:36:56 -08003036 if (rrect.isOval()) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003037 return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(),
Robert Phillips7c525e62018-06-12 10:11:12 -04003038 GrStyle(stroke, nullptr), shaderCaps);
joshualitt3e708c52015-04-30 13:49:27 -07003039 }
3040
3041 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
robertphillips0cc2f852016-02-24 13:36:56 -08003042 return nullptr;
joshualitt3e708c52015-04-30 13:49:27 -07003043 }
3044
Greg Daniel2655ede2019-04-10 00:49:28 +00003045 return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003046}
joshualitt3e708c52015-04-30 13:49:27 -07003047
bsalomon4b4a7cc2016-07-08 04:42:54 -07003048///////////////////////////////////////////////////////////////////////////////
3049
Jim Van Verth64b85892019-06-17 12:01:46 -04003050std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context,
3051 GrPaint&& paint,
3052 const SkMatrix& viewMatrix,
3053 const SkRect& oval,
3054 const GrStyle& style,
3055 const GrShaderCaps* shaderCaps) {
3056 SkScalar width = oval.width();
3057 SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3058 circle_stays_circle(viewMatrix));
3059
3060 auto r = width / 2.f;
3061 SkPoint center = { oval.centerX(), oval.centerY() };
3062 if (style.hasNonDashPathEffect()) {
3063 return nullptr;
3064 } else if (style.isDashed()) {
3065 if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
3066 style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
3067 return nullptr;
3068 }
3069 auto onInterval = style.dashIntervals()[0];
3070 auto offInterval = style.dashIntervals()[1];
3071 if (offInterval == 0) {
3072 GrStyle strokeStyle(style.strokeRec(), nullptr);
3073 return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
3074 strokeStyle, shaderCaps);
3075 } else if (onInterval == 0) {
3076 // There is nothing to draw but we have no way to indicate that here.
3077 return nullptr;
3078 }
3079 auto angularOnInterval = onInterval / r;
3080 auto angularOffInterval = offInterval / r;
3081 auto phaseAngle = style.dashPhase() / r;
3082 // Currently this function doesn't accept ovals with different start angles, though
3083 // it could.
3084 static const SkScalar kStartAngle = 0.f;
3085 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
3086 style.strokeRec().getWidth(), kStartAngle,
3087 angularOnInterval, angularOffInterval, phaseAngle);
3088 }
3089 return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
3090}
3091
Robert Phillipsb97da532019-02-12 15:24:12 -05003092std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003093 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003094 const SkMatrix& viewMatrix,
3095 const SkRect& oval,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003096 const GrStyle& style,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003097 const GrShaderCaps* shaderCaps) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07003098 // we can draw circles
bsalomon4f3a0ca2016-08-22 13:14:26 -07003099 SkScalar width = oval.width();
Jim Van Verthd952a992017-04-20 17:25:26 -04003100 if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3101 circle_stays_circle(viewMatrix)) {
Jim Van Verth64b85892019-06-17 12:01:46 -04003102 return MakeCircleOp(context, std::move(paint), viewMatrix, oval, style, shaderCaps);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003103 }
3104
3105 if (style.pathEffect()) {
3106 return nullptr;
bsalomon4b4a7cc2016-07-08 04:42:54 -07003107 }
3108
Stan Ilieveb868aa2017-02-21 11:06:16 -05003109 // prefer the device space ellipse op for batchability
bsalomon4b4a7cc2016-07-08 04:42:54 -07003110 if (viewMatrix.rectStaysRect()) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003111 return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
bsalomon4b4a7cc2016-07-08 04:42:54 -07003112 }
3113
Stan Ilieveb868aa2017-02-21 11:06:16 -05003114 // Otherwise, if we have shader derivative support, render as device-independent
3115 if (shaderCaps->shaderDerivativeSupport()) {
Jim Van Vertha925bb02018-09-25 10:49:52 -04003116 SkScalar a = viewMatrix[SkMatrix::kMScaleX];
3117 SkScalar b = viewMatrix[SkMatrix::kMSkewX];
3118 SkScalar c = viewMatrix[SkMatrix::kMSkewY];
3119 SkScalar d = viewMatrix[SkMatrix::kMScaleY];
3120 // Check for near-degenerate matrix
3121 if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003122 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval,
Jim Van Vertha925bb02018-09-25 10:49:52 -04003123 style.strokeRec());
3124 }
Stan Ilieveb868aa2017-02-21 11:06:16 -05003125 }
3126
bsalomon4b4a7cc2016-07-08 04:42:54 -07003127 return nullptr;
3128}
3129
3130///////////////////////////////////////////////////////////////////////////////
joshualitt3e708c52015-04-30 13:49:27 -07003131
Robert Phillipsb97da532019-02-12 15:24:12 -05003132std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003133 GrPaint&& paint,
3134 const SkMatrix& viewMatrix,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003135 const SkRect& oval, SkScalar startAngle,
3136 SkScalar sweepAngle, bool useCenter,
3137 const GrStyle& style,
3138 const GrShaderCaps* shaderCaps) {
bsalomon21af9ca2016-08-25 12:29:23 -07003139 SkASSERT(!oval.isEmpty());
3140 SkASSERT(sweepAngle);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003141 SkScalar width = oval.width();
bsalomon21af9ca2016-08-25 12:29:23 -07003142 if (SkScalarAbs(sweepAngle) >= 360.f) {
3143 return nullptr;
3144 }
bsalomon4f3a0ca2016-08-22 13:14:26 -07003145 if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) {
3146 return nullptr;
3147 }
3148 SkPoint center = {oval.centerX(), oval.centerY()};
Brian Salomon289e3d82016-12-14 15:52:56 -05003149 CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle),
3150 useCenter};
Greg Daniel2655ede2019-04-10 00:49:28 +00003151 return CircleOp::Make(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04003152 center, width / 2.f, style, &arcParams);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003153}
3154
3155///////////////////////////////////////////////////////////////////////////////
3156
Hal Canary6f6961e2017-01-31 13:50:44 -05003157#if GR_TEST_UTILS
joshualitt3e708c52015-04-30 13:49:27 -07003158
Brian Salomon05441c42017-05-15 16:45:49 -04003159GR_DRAW_OP_TEST_DEFINE(CircleOp) {
bsalomon4f3a0ca2016-08-22 13:14:26 -07003160 do {
bsalomoncadf75a2016-08-22 14:24:24 -07003161 SkScalar rotate = random->nextSScalar1() * 360.f;
3162 SkScalar translateX = random->nextSScalar1() * 1000.f;
3163 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003164 SkScalar scale;
3165 do {
3166 scale = random->nextSScalar1() * 100.f;
3167 } while (scale == 0);
bsalomoncadf75a2016-08-22 14:24:24 -07003168 SkMatrix viewMatrix;
3169 viewMatrix.setRotate(rotate);
3170 viewMatrix.postTranslate(translateX, translateY);
3171 viewMatrix.postScale(scale, scale);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003172 SkRect circle = GrTest::TestSquare(random);
3173 SkPoint center = {circle.centerX(), circle.centerY()};
3174 SkScalar radius = circle.width() / 2.f;
3175 SkStrokeRec stroke = GrTest::TestStrokeRec(random);
Brian Salomon289e3d82016-12-14 15:52:56 -05003176 CircleOp::ArcParams arcParamsTmp;
3177 const CircleOp::ArcParams* arcParams = nullptr;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003178 if (random->nextBool()) {
3179 arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2;
robertphillips08197b22016-08-23 06:19:15 -07003180 arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f;
3181 arcParamsTmp.fUseCenter = random->nextBool();
bsalomon4f3a0ca2016-08-22 13:14:26 -07003182 arcParams = &arcParamsTmp;
3183 }
Greg Daniel2655ede2019-04-10 00:49:28 +00003184 std::unique_ptr<GrDrawOp> op = CircleOp::Make(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04003185 center, radius,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003186 GrStyle(stroke, nullptr), arcParams);
Brian Salomon289e3d82016-12-14 15:52:56 -05003187 if (op) {
Brian Salomon5ec9def2016-12-20 15:34:05 -05003188 return op;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003189 }
Mike Klein16885072018-12-11 09:54:31 -05003190 assert_alive(paint);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003191 } while (true);
joshualitt3e708c52015-04-30 13:49:27 -07003192}
3193
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003194GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) {
3195 SkScalar rotate = random->nextSScalar1() * 360.f;
3196 SkScalar translateX = random->nextSScalar1() * 1000.f;
3197 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003198 SkScalar scale;
3199 do {
3200 scale = random->nextSScalar1() * 100.f;
3201 } while (scale == 0);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003202 SkMatrix viewMatrix;
3203 viewMatrix.setRotate(rotate);
3204 viewMatrix.postTranslate(translateX, translateY);
3205 viewMatrix.postScale(scale, scale);
3206 SkRect circle = GrTest::TestSquare(random);
3207 SkPoint center = {circle.centerX(), circle.centerY()};
3208 SkScalar radius = circle.width() / 2.f;
3209 SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius);
3210 SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f);
3211 SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f);
3212 SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f);
3213 SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f);
Robert Phillips7c525e62018-06-12 10:11:12 -04003214 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix,
3215 center, radius, strokeWidth,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003216 startAngle, onAngle, offAngle, phase);
3217}
3218
Brian Salomon05441c42017-05-15 16:45:49 -04003219GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003220 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt6c891102015-05-13 08:51:49 -07003221 SkRect ellipse = GrTest::TestSquare(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003222 return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
Robert Phillips7c525e62018-06-12 10:11:12 -04003223 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003224}
3225
Brian Salomon05441c42017-05-15 16:45:49 -04003226GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003227 SkMatrix viewMatrix = GrTest::TestMatrix(random);
joshualitt6c891102015-05-13 08:51:49 -07003228 SkRect ellipse = GrTest::TestSquare(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003229 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
Robert Phillips7c525e62018-06-12 10:11:12 -04003230 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003231}
3232
Jim Van Verth64b85892019-06-17 12:01:46 -04003233GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) {
3234 do {
3235 SkScalar rotate = random->nextSScalar1() * 360.f;
3236 SkScalar translateX = random->nextSScalar1() * 1000.f;
3237 SkScalar translateY = random->nextSScalar1() * 1000.f;
3238 SkScalar scale;
3239 do {
3240 scale = random->nextSScalar1() * 100.f;
3241 } while (scale == 0);
3242 SkMatrix viewMatrix;
3243 viewMatrix.setRotate(rotate);
3244 viewMatrix.postTranslate(translateX, translateY);
3245 viewMatrix.postScale(scale, scale);
3246 SkRect rect = GrTest::TestRect(random);
3247 SkScalar radius = random->nextRangeF(0.1f, 10.f);
3248 SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius);
3249 if (rrect.isOval()) {
3250 continue;
3251 }
3252 std::unique_ptr<GrDrawOp> op =
3253 GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect,
3254 GrTest::TestStrokeRec(random), nullptr);
3255 if (op) {
3256 return op;
3257 }
3258 assert_alive(paint);
3259 } while (true);
3260}
3261
Brian Salomon05441c42017-05-15 16:45:49 -04003262GR_DRAW_OP_TEST_DEFINE(RRectOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003263 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt3e708c52015-04-30 13:49:27 -07003264 const SkRRect& rrect = GrTest::TestRRectSimple(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003265 return make_rrect_op(context, std::move(paint), viewMatrix, rrect,
Robert Phillips7c525e62018-06-12 10:11:12 -04003266 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003267}
3268
3269#endif