blob: 0ea5f95de2063f3a0a77f189d723df344bebc05e [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");
jvanverth84839f62016-08-29 10:16:40 -07002286}
2287
2288static int rrect_type_to_index_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002289 switch (type) {
2290 case kFill_RRectType:
2291 return kIndicesPerFillRRect;
2292 case kStroke_RRectType:
2293 return kIndicesPerStrokeRRect;
2294 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002295 return kIndicesPerOverstrokeRRect;
2296 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002297 SK_ABORT("Invalid type");
jvanverth84839f62016-08-29 10:16:40 -07002298}
2299
2300static const uint16_t* rrect_type_to_indices(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002301 switch (type) {
2302 case kFill_RRectType:
2303 case kStroke_RRectType:
2304 return gStandardRRectIndices;
2305 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002306 return gOverstrokeRRectIndices;
2307 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002308 SK_ABORT("Invalid type");
bsalomoned0bcad2015-05-04 10:36:42 -07002309}
2310
joshualitt76e7fb62015-02-11 08:52:27 -08002311///////////////////////////////////////////////////////////////////////////////////////////////////
2312
Robert Phillips79839d42016-10-06 15:03:34 -04002313// For distance computations in the interior of filled rrects we:
2314//
2315// add a interior degenerate (point or line) rect
2316// each vertex of that rect gets -outerRad as its radius
2317// this makes the computation of the distance to the outer edge be negative
2318// negative values are caught and then handled differently in the GP's onEmitCode
2319// each vertex is also given the normalized x & y distance from the interior rect's edge
2320// the GP takes the min of those depths +1 to get the normalized distance to the outer edge
2321
Brian Salomon05441c42017-05-15 16:45:49 -04002322class CircularRRectOp : public GrMeshDrawOp {
2323private:
2324 using Helper = GrSimpleMeshDrawOpHelper;
2325
joshualitt76e7fb62015-02-11 08:52:27 -08002326public:
Brian Salomon25a88092016-12-01 09:36:50 -05002327 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002328
bsalomon4b4a7cc2016-07-08 04:42:54 -07002329 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
2330 // whether the rrect is only stroked or stroked and filled.
Robert Phillipsb97da532019-02-12 15:24:12 -05002331 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002332 GrPaint&& paint,
2333 const SkMatrix& viewMatrix,
2334 const SkRect& devRect,
2335 float devRadius,
2336 float devStrokeWidth,
2337 bool strokeOnly) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002338 return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04002339 devRect, devRadius,
2340 devStrokeWidth, strokeOnly);
Brian Salomon05441c42017-05-15 16:45:49 -04002341 }
Greg Daniel2655ede2019-04-10 00:49:28 +00002342 CircularRRectOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002343 const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius,
2344 float devStrokeWidth, bool strokeOnly)
Brian Salomon05441c42017-05-15 16:45:49 -04002345 : INHERITED(ClassID())
2346 , fViewMatrixIfUsingLocalCoords(viewMatrix)
Greg Daniel2655ede2019-04-10 00:49:28 +00002347 , fHelper(helperArgs, GrAAType::kCoverage) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002348 SkRect bounds = devRect;
2349 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
2350 SkScalar innerRadius = 0.0f;
2351 SkScalar outerRadius = devRadius;
2352 SkScalar halfWidth = 0;
jvanverth84839f62016-08-29 10:16:40 -07002353 RRectType type = kFill_RRectType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002354 if (devStrokeWidth > 0) {
2355 if (SkScalarNearlyZero(devStrokeWidth)) {
2356 halfWidth = SK_ScalarHalf;
2357 } else {
2358 halfWidth = SkScalarHalf(devStrokeWidth);
2359 }
joshualitt76e7fb62015-02-11 08:52:27 -08002360
bsalomon4b4a7cc2016-07-08 04:42:54 -07002361 if (strokeOnly) {
jvanverthc3d0e422016-08-25 08:12:35 -07002362 // Outset stroke by 1/4 pixel
2363 devStrokeWidth += 0.25f;
2364 // If stroke is greater than width or height, this is still a fill
2365 // Otherwise we compute stroke params
Brian Salomon289e3d82016-12-14 15:52:56 -05002366 if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) {
jvanverthc3d0e422016-08-25 08:12:35 -07002367 innerRadius = devRadius - halfWidth;
jvanverth84839f62016-08-29 10:16:40 -07002368 type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
jvanverthc3d0e422016-08-25 08:12:35 -07002369 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002370 }
2371 outerRadius += halfWidth;
2372 bounds.outset(halfWidth, halfWidth);
2373 }
bsalomoncdaa97b2016-03-08 08:30:14 -08002374
Greg Daniel2655ede2019-04-10 00:49:28 +00002375 // The radii are outset for two reasons. First, it allows the shader to simply perform
2376 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
2377 // Second, the outer radius is used to compute the verts of the bounding box that is
2378 // rendered and the outset ensures the box will cover all partially covered by the rrect
2379 // corners.
2380 outerRadius += SK_ScalarHalf;
2381 innerRadius -= SK_ScalarHalf;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002382
Greg Daniel2655ede2019-04-10 00:49:28 +00002383 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07002384
Greg Daniel2655ede2019-04-10 00:49:28 +00002385 // Expand the rect for aa to generate correct vertices.
2386 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002387
Brian Salomon05441c42017-05-15 16:45:49 -04002388 fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type});
jvanverth84839f62016-08-29 10:16:40 -07002389 fVertCount = rrect_type_to_vert_count(type);
2390 fIndexCount = rrect_type_to_index_count(type);
2391 fAllFill = (kFill_RRectType == type);
joshualitt76e7fb62015-02-11 08:52:27 -08002392 }
2393
Brian Salomon289e3d82016-12-14 15:52:56 -05002394 const char* name() const override { return "CircularRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002395
Chris Dalton1706cbf2019-05-21 19:35:29 -06002396 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002397 fHelper.visitProxies(func);
2398 }
2399
Brian Osman9a390ac2018-11-12 09:47:48 -05002400#ifdef SK_DEBUG
jvanverthc3d0e422016-08-25 08:12:35 -07002401 SkString dumpInfo() const override {
2402 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002403 for (int i = 0; i < fRRects.count(); ++i) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002404 string.appendf(
2405 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
2406 "InnerRad: %.2f, OuterRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002407 fRRects[i].fColor.toBytes_RGBA(), fRRects[i].fDevBounds.fLeft,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002408 fRRects[i].fDevBounds.fTop, fRRects[i].fDevBounds.fRight,
2409 fRRects[i].fDevBounds.fBottom, fRRects[i].fInnerRadius,
2410 fRRects[i].fOuterRadius);
jvanverthc3d0e422016-08-25 08:12:35 -07002411 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002412 string += fHelper.dumpInfo();
2413 string += INHERITED::dumpInfo();
jvanverthc3d0e422016-08-25 08:12:35 -07002414 return string;
2415 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002416#endif
jvanverthc3d0e422016-08-25 08:12:35 -07002417
Chris Dalton6ce447a2019-06-23 18:07:38 -06002418 GrProcessorSet::Analysis finalize(
2419 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
2420 GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04002421 SkPMColor4f* color = &fRRects.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06002422 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002423 GrProcessorAnalysisCoverage::kSingleChannel, color,
2424 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002425 }
2426
2427 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2428
Brian Salomon92aee3d2016-12-21 09:20:25 -05002429private:
Brian Osmana1d4eb92018-12-06 16:33:10 -05002430 static void FillInOverstrokeVerts(GrVertexWriter& verts, const SkRect& bounds, SkScalar smInset,
Brian Salomon289e3d82016-12-14 15:52:56 -05002431 SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002432 SkScalar innerRadius, const GrVertexColor& color) {
Robert Phillips79839d42016-10-06 15:03:34 -04002433 SkASSERT(smInset < bigInset);
2434
2435 // TL
Brian Osmana1d4eb92018-12-06 16:33:10 -05002436 verts.write(bounds.fLeft + smInset, bounds.fTop + smInset,
2437 color,
2438 xOffset, 0.0f,
2439 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002440
2441 // TR
Brian Osmana1d4eb92018-12-06 16:33:10 -05002442 verts.write(bounds.fRight - smInset, bounds.fTop + smInset,
2443 color,
2444 xOffset, 0.0f,
2445 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002446
Brian Osmana1d4eb92018-12-06 16:33:10 -05002447 verts.write(bounds.fLeft + bigInset, bounds.fTop + bigInset,
2448 color,
2449 0.0f, 0.0f,
2450 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002451
Brian Osmana1d4eb92018-12-06 16:33:10 -05002452 verts.write(bounds.fRight - bigInset, bounds.fTop + bigInset,
2453 color,
2454 0.0f, 0.0f,
2455 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002456
Brian Osmana1d4eb92018-12-06 16:33:10 -05002457 verts.write(bounds.fLeft + bigInset, bounds.fBottom - bigInset,
2458 color,
2459 0.0f, 0.0f,
2460 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002461
Brian Osmana1d4eb92018-12-06 16:33:10 -05002462 verts.write(bounds.fRight - bigInset, bounds.fBottom - bigInset,
2463 color,
2464 0.0f, 0.0f,
2465 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002466
2467 // BL
Brian Osmana1d4eb92018-12-06 16:33:10 -05002468 verts.write(bounds.fLeft + smInset, bounds.fBottom - smInset,
2469 color,
2470 xOffset, 0.0f,
2471 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002472
2473 // BR
Brian Osmana1d4eb92018-12-06 16:33:10 -05002474 verts.write(bounds.fRight - smInset, bounds.fBottom - smInset,
2475 color,
2476 xOffset, 0.0f,
2477 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002478 }
2479
Brian Salomon91326c32017-08-09 16:02:19 -04002480 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002481 // Invert the view matrix as a local matrix (if any other processors require coords).
2482 SkMatrix localMatrix;
2483 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002484 return;
2485 }
2486
2487 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002488 sk_sp<GrGeometryProcessor> gp(
Greg Daniel2655ede2019-04-10 00:49:28 +00002489 new CircleGeometryProcessor(!fAllFill, false, false, false, false, fWideColor,
2490 localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002491
Brian Salomon12d22642019-01-29 14:38:50 -05002492 sk_sp<const GrBuffer> vertexBuffer;
jvanverth84839f62016-08-29 10:16:40 -07002493 int firstVertex;
joshualitt76e7fb62015-02-11 08:52:27 -08002494
Brian Osmana1d4eb92018-12-06 16:33:10 -05002495 GrVertexWriter verts{target->makeVertexSpace(gp->vertexStride(), fVertCount,
2496 &vertexBuffer, &firstVertex)};
2497 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002498 SkDebugf("Could not allocate vertices\n");
2499 return;
2500 }
2501
Brian Salomon12d22642019-01-29 14:38:50 -05002502 sk_sp<const GrBuffer> indexBuffer;
jvanverth84839f62016-08-29 10:16:40 -07002503 int firstIndex = 0;
2504 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
2505 if (!indices) {
2506 SkDebugf("Could not allocate indices\n");
2507 return;
2508 }
2509
2510 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04002511 for (const auto& rrect : fRRects) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002512 GrVertexColor color(rrect.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002513 SkScalar outerRadius = rrect.fOuterRadius;
2514 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002515
Brian Salomon289e3d82016-12-14 15:52:56 -05002516 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius,
2517 bounds.fBottom - outerRadius, bounds.fBottom};
joshualitt76e7fb62015-02-11 08:52:27 -08002518
Brian Salomon289e3d82016-12-14 15:52:56 -05002519 SkScalar yOuterRadii[4] = {-1, 0, 0, 1};
joshualitt76e7fb62015-02-11 08:52:27 -08002520 // The inner radius in the vertex data must be specified in normalized space.
jvanverth84839f62016-08-29 10:16:40 -07002521 // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
Brian Salomone23bffd2017-06-02 11:01:10 -04002522 SkScalar innerRadius = rrect.fType != kFill_RRectType
2523 ? rrect.fInnerRadius / rrect.fOuterRadius
2524 : -1.0f / rrect.fOuterRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002525 for (int i = 0; i < 4; ++i) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002526 verts.write(bounds.fLeft, yCoords[i],
2527 color,
2528 -1.0f, yOuterRadii[i],
2529 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002530
Brian Osmana1d4eb92018-12-06 16:33:10 -05002531 verts.write(bounds.fLeft + outerRadius, yCoords[i],
2532 color,
2533 0.0f, yOuterRadii[i],
2534 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002535
Brian Osmana1d4eb92018-12-06 16:33:10 -05002536 verts.write(bounds.fRight - outerRadius, yCoords[i],
2537 color,
2538 0.0f, yOuterRadii[i],
2539 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002540
Brian Osmana1d4eb92018-12-06 16:33:10 -05002541 verts.write(bounds.fRight, yCoords[i],
2542 color,
2543 1.0f, yOuterRadii[i],
2544 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002545 }
jvanverthc3d0e422016-08-25 08:12:35 -07002546 // Add the additional vertices for overstroked rrects.
jvanvertha4f1af82016-08-29 07:17:47 -07002547 // Effectively this is an additional stroked rrect, with its
2548 // outer radius = outerRadius - innerRadius, and inner radius = 0.
2549 // This will give us correct AA in the center and the correct
2550 // distance to the outer edge.
jvanverthc3d0e422016-08-25 08:12:35 -07002551 //
jvanvertha4f1af82016-08-29 07:17:47 -07002552 // Also, the outer offset is a constant vector pointing to the right, which
2553 // guarantees that the distance value along the outer rectangle is constant.
Brian Salomon05441c42017-05-15 16:45:49 -04002554 if (kOverstroke_RRectType == rrect.fType) {
2555 SkASSERT(rrect.fInnerRadius <= 0.0f);
Robert Phillips79839d42016-10-06 15:03:34 -04002556
Brian Salomon05441c42017-05-15 16:45:49 -04002557 SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002558 // this is the normalized distance from the outer rectangle of this
2559 // geometry to the outer edge
Brian Salomon05441c42017-05-15 16:45:49 -04002560 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002561
Brian Osmana1d4eb92018-12-06 16:33:10 -05002562 FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002563 overstrokeOuterRadius, 0.0f, color);
Robert Phillips79839d42016-10-06 15:03:34 -04002564 }
jvanverth6a397612016-08-26 08:15:33 -07002565
Brian Salomon05441c42017-05-15 16:45:49 -04002566 const uint16_t* primIndices = rrect_type_to_indices(rrect.fType);
2567 const int primIndexCount = rrect_type_to_index_count(rrect.fType);
jvanverth84839f62016-08-29 10:16:40 -07002568 for (int i = 0; i < primIndexCount; ++i) {
2569 *indices++ = primIndices[i] + currStartVertex;
2570 }
2571
Brian Salomon05441c42017-05-15 16:45:49 -04002572 currStartVertex += rrect_type_to_vert_count(rrect.fType);
joshualitt76e7fb62015-02-11 08:52:27 -08002573 }
2574
Brian Salomon7eae3e02018-08-07 14:02:38 +00002575 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05002576 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00002577 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05002578 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002579 target->recordDraw(std::move(gp), mesh);
2580 }
2581
2582 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2583 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002584 }
2585
Brian Salomon7eae3e02018-08-07 14:02:38 +00002586 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002587 CircularRRectOp* that = t->cast<CircularRRectOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05002588
2589 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05002590 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002591 return CombineResult::kCannotCombine;
Jim Van Verth8cefe402017-02-09 11:36:37 -05002592 }
2593
Brian Salomon05441c42017-05-15 16:45:49 -04002594 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002595 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002596 }
2597
Brian Salomon05441c42017-05-15 16:45:49 -04002598 if (fHelper.usesLocalCoords() &&
2599 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002600 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002601 }
2602
Brian Salomon05441c42017-05-15 16:45:49 -04002603 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
jvanverth84839f62016-08-29 10:16:40 -07002604 fVertCount += that->fVertCount;
2605 fIndexCount += that->fIndexCount;
2606 fAllFill = fAllFill && that->fAllFill;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002607 fWideColor = fWideColor || that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002608 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002609 }
2610
Brian Salomon05441c42017-05-15 16:45:49 -04002611 struct RRect {
Brian Osmancf860852018-10-31 14:04:39 -04002612 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002613 SkScalar fInnerRadius;
2614 SkScalar fOuterRadius;
2615 SkRect fDevBounds;
jvanverth84839f62016-08-29 10:16:40 -07002616 RRectType fType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002617 };
2618
Brian Salomon289e3d82016-12-14 15:52:56 -05002619 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002620 Helper fHelper;
Brian Salomon289e3d82016-12-14 15:52:56 -05002621 int fVertCount;
2622 int fIndexCount;
2623 bool fAllFill;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002624 bool fWideColor;
Brian Salomon05441c42017-05-15 16:45:49 -04002625 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002626
Brian Salomon05441c42017-05-15 16:45:49 -04002627 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002628};
2629
jvanverth84839f62016-08-29 10:16:40 -07002630static const int kNumRRectsInIndexBuffer = 256;
2631
2632GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2633GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
Brian Salomond28a79d2017-10-16 13:01:07 -04002634static sk_sp<const GrBuffer> get_rrect_index_buffer(RRectType type,
2635 GrResourceProvider* resourceProvider) {
jvanverth84839f62016-08-29 10:16:40 -07002636 GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2637 GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
2638 switch (type) {
2639 case kFill_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002640 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002641 gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer,
2642 kVertsPerStandardRRect, gRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002643 case kStroke_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002644 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002645 gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer,
2646 kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002647 default:
2648 SkASSERT(false);
2649 return nullptr;
Brian Salomon23356442018-11-30 15:33:19 -05002650 }
jvanverth84839f62016-08-29 10:16:40 -07002651}
2652
Brian Salomon05441c42017-05-15 16:45:49 -04002653class EllipticalRRectOp : public GrMeshDrawOp {
2654private:
2655 using Helper = GrSimpleMeshDrawOpHelper;
2656
joshualitt76e7fb62015-02-11 08:52:27 -08002657public:
Brian Salomon25a88092016-12-01 09:36:50 -05002658 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002659
bsalomon4b4a7cc2016-07-08 04:42:54 -07002660 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
2661 // whether the rrect is only stroked or stroked and filled.
Robert Phillipsb97da532019-02-12 15:24:12 -05002662 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002663 GrPaint&& paint,
2664 const SkMatrix& viewMatrix,
2665 const SkRect& devRect,
2666 float devXRadius,
2667 float devYRadius,
2668 SkVector devStrokeWidths,
2669 bool strokeOnly) {
Brian Osman9fb7fa52019-07-01 16:48:39 -04002670 SkASSERT(devXRadius >= 0.5);
2671 SkASSERT(devYRadius >= 0.5);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002672 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0));
2673 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0));
bsalomon4b4a7cc2016-07-08 04:42:54 -07002674 if (devStrokeWidths.fX > 0) {
2675 if (SkScalarNearlyZero(devStrokeWidths.length())) {
2676 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf);
2677 } else {
2678 devStrokeWidths.scale(SK_ScalarHalf);
2679 }
joshualitt76e7fb62015-02-11 08:52:27 -08002680
bsalomon4b4a7cc2016-07-08 04:42:54 -07002681 // we only handle thick strokes for near-circular ellipses
2682 if (devStrokeWidths.length() > SK_ScalarHalf &&
Brian Salomon289e3d82016-12-14 15:52:56 -05002683 (SK_ScalarHalf * devXRadius > devYRadius ||
2684 SK_ScalarHalf * devYRadius > devXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002685 return nullptr;
2686 }
2687
2688 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon289e3d82016-12-14 15:52:56 -05002689 if (devStrokeWidths.fX * (devYRadius * devYRadius) <
2690 (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002691 return nullptr;
2692 }
Brian Salomon289e3d82016-12-14 15:52:56 -05002693 if (devStrokeWidths.fY * (devXRadius * devXRadius) <
2694 (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002695 return nullptr;
2696 }
Brian Salomon05441c42017-05-15 16:45:49 -04002697 }
Robert Phillips7c525e62018-06-12 10:11:12 -04002698 return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint),
Greg Daniel2655ede2019-04-10 00:49:28 +00002699 viewMatrix, devRect,
Brian Salomon05441c42017-05-15 16:45:49 -04002700 devXRadius, devYRadius, devStrokeWidths,
2701 strokeOnly);
2702 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002703
Greg Daniel2655ede2019-04-10 00:49:28 +00002704 EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002705 const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius,
2706 float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002707 : INHERITED(ClassID())
2708 , fHelper(helperArgs, GrAAType::kCoverage)
2709 , fUseScale(false) {
Brian Salomon05441c42017-05-15 16:45:49 -04002710 SkScalar innerXRadius = 0.0f;
2711 SkScalar innerYRadius = 0.0f;
2712 SkRect bounds = devRect;
2713 bool stroked = false;
2714 if (devStrokeHalfWidths.fX > 0) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002715 // this is legit only if scale & translation (which should be the case at the moment)
2716 if (strokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04002717 innerXRadius = devXRadius - devStrokeHalfWidths.fX;
2718 innerYRadius = devYRadius - devStrokeHalfWidths.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002719 stroked = (innerXRadius >= 0 && innerYRadius >= 0);
2720 }
2721
Brian Salomon05441c42017-05-15 16:45:49 -04002722 devXRadius += devStrokeHalfWidths.fX;
2723 devYRadius += devStrokeHalfWidths.fY;
2724 bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002725 }
2726
Brian Salomon05441c42017-05-15 16:45:49 -04002727 fStroked = stroked;
2728 fViewMatrixIfUsingLocalCoords = viewMatrix;
Greg Daniel2655ede2019-04-10 00:49:28 +00002729 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
2730 // Expand the rect for aa in order to generate the correct vertices.
2731 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
Brian Salomon05441c42017-05-15 16:45:49 -04002732 fRRects.emplace_back(
2733 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
joshualitt76e7fb62015-02-11 08:52:27 -08002734 }
2735
Brian Salomon289e3d82016-12-14 15:52:56 -05002736 const char* name() const override { return "EllipticalRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002737
Chris Dalton1706cbf2019-05-21 19:35:29 -06002738 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002739 fHelper.visitProxies(func);
2740 }
2741
Brian Osman9a390ac2018-11-12 09:47:48 -05002742#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05002743 SkString dumpInfo() const override {
2744 SkString string;
2745 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04002746 for (const auto& geo : fRRects) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002747 string.appendf(
2748 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
2749 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002750 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002751 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
2752 geo.fInnerXRadius, geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002753 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002754 string += fHelper.dumpInfo();
2755 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002756 return string;
2757 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002758#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05002759
Chris Dalton6ce447a2019-06-23 18:07:38 -06002760 GrProcessorSet::Analysis finalize(
2761 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
2762 GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002763 fUseScale = !caps.shaderCaps()->floatIs32Bits();
Brian Osmancf860852018-10-31 14:04:39 -04002764 SkPMColor4f* color = &fRRects.front().fColor;
Chris Dalton6ce447a2019-06-23 18:07:38 -06002765 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002766 GrProcessorAnalysisCoverage::kSingleChannel, color,
2767 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002768 }
2769
2770 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2771
bsalomone46f9fe2015-08-18 06:05:14 -07002772private:
Brian Salomon91326c32017-08-09 16:02:19 -04002773 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002774 SkMatrix localMatrix;
2775 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002776 return;
2777 }
2778
2779 // Setup geometry processor
Greg Daniel2655ede2019-04-10 00:49:28 +00002780 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002781 localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002782
bsalomonb5238a72015-05-05 07:49:49 -07002783 // drop out the middle quad if we're stroked
jvanverthc3d0e422016-08-25 08:12:35 -07002784 int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
Brian Salomond28a79d2017-10-16 13:01:07 -04002785 sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer(
2786 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider());
joshualitt76e7fb62015-02-11 08:52:27 -08002787
Brian Salomon12d22642019-01-29 14:38:50 -05002788 if (!indexBuffer) {
2789 SkDebugf("Could not allocate indices\n");
2790 return;
2791 }
Brian Osmana1d4eb92018-12-06 16:33:10 -05002792 PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(),
Brian Salomon12d22642019-01-29 14:38:50 -05002793 std::move(indexBuffer), kVertsPerStandardRRect, indicesPerInstance,
Brian Salomon7eae3e02018-08-07 14:02:38 +00002794 fRRects.count());
Brian Osmana1d4eb92018-12-06 16:33:10 -05002795 GrVertexWriter verts{helper.vertices()};
Brian Salomon12d22642019-01-29 14:38:50 -05002796 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002797 SkDebugf("Could not allocate vertices\n");
2798 return;
2799 }
2800
Brian Salomon05441c42017-05-15 16:45:49 -04002801 for (const auto& rrect : fRRects) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002802 GrVertexColor color(rrect.fColor, fWideColor);
joshualitt76e7fb62015-02-11 08:52:27 -08002803 // Compute the reciprocals of the radii here to save time in the shader
Brian Osmana1d4eb92018-12-06 16:33:10 -05002804 float reciprocalRadii[4] = {
2805 SkScalarInvert(rrect.fXRadius),
2806 SkScalarInvert(rrect.fYRadius),
2807 SkScalarInvert(rrect.fInnerXRadius),
2808 SkScalarInvert(rrect.fInnerYRadius)
2809 };
joshualitt76e7fb62015-02-11 08:52:27 -08002810
2811 // Extend the radii out half a pixel to antialias.
Greg Daniel2655ede2019-04-10 00:49:28 +00002812 SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf;
2813 SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf;
joshualitt76e7fb62015-02-11 08:52:27 -08002814
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002815 SkScalar xMaxOffset = xOuterRadius;
2816 SkScalar yMaxOffset = yOuterRadius;
2817 if (!fStroked) {
2818 // For filled rrects we map a unit circle in the vertex attributes rather than
2819 // computing an ellipse and modifying that distance, so we normalize to 1.
2820 xMaxOffset /= rrect.fXRadius;
2821 yMaxOffset /= rrect.fYRadius;
2822 }
2823
Brian Salomon05441c42017-05-15 16:45:49 -04002824 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002825
Brian Salomon289e3d82016-12-14 15:52:56 -05002826 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius,
2827 bounds.fBottom - yOuterRadius, bounds.fBottom};
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002828 SkScalar yOuterOffsets[4] = {yMaxOffset,
Brian Salomon289e3d82016-12-14 15:52:56 -05002829 SK_ScalarNearlyZero, // we're using inversesqrt() in
2830 // shader, so can't be exactly 0
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002831 SK_ScalarNearlyZero, yMaxOffset};
joshualitt76e7fb62015-02-11 08:52:27 -08002832
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002833 auto maybeScale = GrVertexWriter::If(fUseScale, SkTMax(rrect.fXRadius, rrect.fYRadius));
joshualitt76e7fb62015-02-11 08:52:27 -08002834 for (int i = 0; i < 4; ++i) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002835 verts.write(bounds.fLeft, yCoords[i],
2836 color,
2837 xMaxOffset, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002838 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002839 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002840
Brian Osmana1d4eb92018-12-06 16:33:10 -05002841 verts.write(bounds.fLeft + xOuterRadius, yCoords[i],
2842 color,
2843 SK_ScalarNearlyZero, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002844 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002845 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002846
Brian Osmana1d4eb92018-12-06 16:33:10 -05002847 verts.write(bounds.fRight - xOuterRadius, yCoords[i],
2848 color,
2849 SK_ScalarNearlyZero, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002850 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002851 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002852
Brian Osmana1d4eb92018-12-06 16:33:10 -05002853 verts.write(bounds.fRight, yCoords[i],
2854 color,
2855 xMaxOffset, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002856 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002857 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002858 }
2859 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002860 helper.recordDraw(target, std::move(gp));
2861 }
2862
2863 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2864 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002865 }
2866
Brian Salomon7eae3e02018-08-07 14:02:38 +00002867 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002868 EllipticalRRectOp* that = t->cast<EllipticalRRectOp>();
bsalomonabd30f52015-08-13 13:34:48 -07002869
Brian Salomon05441c42017-05-15 16:45:49 -04002870 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002871 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002872 }
2873
bsalomoncdaa97b2016-03-08 08:30:14 -08002874 if (fStroked != that->fStroked) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002875 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002876 }
2877
Brian Salomon05441c42017-05-15 16:45:49 -04002878 if (fHelper.usesLocalCoords() &&
2879 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002880 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002881 }
Brian Salomonea26d6b2018-01-23 20:33:21 +00002882
Brian Salomon05441c42017-05-15 16:45:49 -04002883 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
Brian Osmana1d4eb92018-12-06 16:33:10 -05002884 fWideColor = fWideColor || that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002885 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002886 }
2887
Brian Salomon05441c42017-05-15 16:45:49 -04002888 struct RRect {
Brian Osmancf860852018-10-31 14:04:39 -04002889 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002890 SkScalar fXRadius;
2891 SkScalar fYRadius;
2892 SkScalar fInnerXRadius;
2893 SkScalar fInnerYRadius;
2894 SkRect fDevBounds;
2895 };
2896
Brian Salomon289e3d82016-12-14 15:52:56 -05002897 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002898 Helper fHelper;
2899 bool fStroked;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002900 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002901 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04002902 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002903
Brian Salomon05441c42017-05-15 16:45:49 -04002904 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002905};
2906
Jim Van Verth64b85892019-06-17 12:01:46 -04002907std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context,
2908 GrPaint&& paint,
2909 const SkMatrix& viewMatrix,
2910 const SkRRect& rrect,
2911 const SkStrokeRec& stroke,
2912 const GrShaderCaps* shaderCaps) {
2913 SkASSERT(viewMatrix.rectStaysRect());
2914 SkASSERT(viewMatrix.isSimilarity());
2915 SkASSERT(rrect.isSimple());
2916 SkASSERT(!rrect.isOval());
2917 SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY);
2918
2919 // RRect ops only handle simple, but not too simple, rrects.
2920 // Do any matrix crunching before we reset the draw state for device coords.
2921 const SkRect& rrectBounds = rrect.getBounds();
2922 SkRect bounds;
2923 viewMatrix.mapRect(&bounds, rrectBounds);
2924
2925 SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
2926 SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] +
2927 viewMatrix[SkMatrix::kMSkewY]));
2928
2929 // Do mapping of stroke. Use -1 to indicate fill-only draws.
2930 SkScalar scaledStroke = -1;
2931 SkScalar strokeWidth = stroke.getWidth();
2932 SkStrokeRec::Style style = stroke.getStyle();
2933
2934 bool isStrokeOnly =
2935 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
2936 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
2937
2938 if (hasStroke) {
2939 if (SkStrokeRec::kHairline_Style == style) {
2940 scaledStroke = SK_Scalar1;
2941 } else {
2942 scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
2943 }
2944 }
2945
2946 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
2947 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
2948 // patch will have fractional coverage. This only matters when the interior is actually filled.
2949 // We could consider falling back to rect rendering here, since a tiny radius is
2950 // indistinguishable from a square corner.
2951 if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) {
2952 return nullptr;
2953 }
2954
2955 return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius,
2956 scaledStroke, isStrokeOnly);
2957}
2958
Robert Phillipsb97da532019-02-12 15:24:12 -05002959static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002960 GrPaint&& paint,
Brian Salomon05441c42017-05-15 16:45:49 -04002961 const SkMatrix& viewMatrix,
2962 const SkRRect& rrect,
2963 const SkStrokeRec& stroke) {
joshualitt3e708c52015-04-30 13:49:27 -07002964 SkASSERT(viewMatrix.rectStaysRect());
2965 SkASSERT(rrect.isSimple());
2966 SkASSERT(!rrect.isOval());
commit-bot@chromium.orge0a868c2013-11-22 07:02:11 +00002967
Brian Salomon53e4c3c2016-12-21 11:38:53 -05002968 // RRect ops only handle simple, but not too simple, rrects.
2969 // Do any matrix crunching before we reset the draw state for device coords.
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002970 const SkRect& rrectBounds = rrect.getBounds();
joshualittd96a67b2015-05-05 14:09:05 -07002971 SkRect bounds;
2972 viewMatrix.mapRect(&bounds, rrectBounds);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002973
Mike Reed242135a2018-02-22 13:41:39 -05002974 SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
Brian Salomon289e3d82016-12-14 15:52:56 -05002975 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
2976 viewMatrix[SkMatrix::kMSkewY] * radii.fY);
2977 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
2978 viewMatrix[SkMatrix::kMScaleY] * radii.fY);
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +00002979
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002980 SkStrokeRec::Style style = stroke.getStyle();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002981
bsalomon4b4a7cc2016-07-08 04:42:54 -07002982 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
2983 SkVector scaledStroke = {-1, -1};
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002984 SkScalar strokeWidth = stroke.getWidth();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002985
Brian Salomon289e3d82016-12-14 15:52:56 -05002986 bool isStrokeOnly =
2987 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002988 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
2989
2990 if (hasStroke) {
2991 if (SkStrokeRec::kHairline_Style == style) {
2992 scaledStroke.set(1, 1);
2993 } else {
Brian Salomon289e3d82016-12-14 15:52:56 -05002994 scaledStroke.fX = SkScalarAbs(
2995 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
2996 scaledStroke.fY = SkScalarAbs(
2997 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002998 }
2999
Jim Van Verth64b85892019-06-17 12:01:46 -04003000 // if half of strokewidth is greater than radius, we don't handle that right now
3001 if ((SK_ScalarHalf * scaledStroke.fX > xRadius ||
3002 SK_ScalarHalf * scaledStroke.fY > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07003003 return nullptr;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003004 }
3005 }
3006
Brian Salomon8a97f562019-04-18 14:07:27 -04003007 // The matrix may have a rotation by an odd multiple of 90 degrees.
Jim Van Verth64b85892019-06-17 12:01:46 -04003008 if (viewMatrix.getScaleX() == 0) {
Brian Salomon8a97f562019-04-18 14:07:27 -04003009 std::swap(xRadius, yRadius);
3010 std::swap(scaledStroke.fX, scaledStroke.fY);
3011 }
3012
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003013 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
3014 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
3015 // patch will have fractional coverage. This only matters when the interior is actually filled.
3016 // We could consider falling back to rect rendering here, since a tiny radius is
3017 // indistinguishable from a square corner.
3018 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07003019 return nullptr;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003020 }
3021
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003022 // if the corners are circles, use the circle renderer
Jim Van Verth64b85892019-06-17 12:01:46 -04003023 return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
3024 xRadius, yRadius, scaledStroke, isStrokeOnly);
joshualitt3e708c52015-04-30 13:49:27 -07003025}
3026
Robert Phillipsb97da532019-02-12 15:24:12 -05003027std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003028 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003029 const SkMatrix& viewMatrix,
3030 const SkRRect& rrect,
3031 const SkStrokeRec& stroke,
3032 const GrShaderCaps* shaderCaps) {
robertphillips0cc2f852016-02-24 13:36:56 -08003033 if (rrect.isOval()) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003034 return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(),
Robert Phillips7c525e62018-06-12 10:11:12 -04003035 GrStyle(stroke, nullptr), shaderCaps);
joshualitt3e708c52015-04-30 13:49:27 -07003036 }
3037
3038 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
robertphillips0cc2f852016-02-24 13:36:56 -08003039 return nullptr;
joshualitt3e708c52015-04-30 13:49:27 -07003040 }
3041
Greg Daniel2655ede2019-04-10 00:49:28 +00003042 return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003043}
joshualitt3e708c52015-04-30 13:49:27 -07003044
bsalomon4b4a7cc2016-07-08 04:42:54 -07003045///////////////////////////////////////////////////////////////////////////////
3046
Jim Van Verth64b85892019-06-17 12:01:46 -04003047std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context,
3048 GrPaint&& paint,
3049 const SkMatrix& viewMatrix,
3050 const SkRect& oval,
3051 const GrStyle& style,
3052 const GrShaderCaps* shaderCaps) {
3053 SkScalar width = oval.width();
3054 SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3055 circle_stays_circle(viewMatrix));
3056
3057 auto r = width / 2.f;
3058 SkPoint center = { oval.centerX(), oval.centerY() };
3059 if (style.hasNonDashPathEffect()) {
3060 return nullptr;
3061 } else if (style.isDashed()) {
3062 if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
3063 style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
3064 return nullptr;
3065 }
3066 auto onInterval = style.dashIntervals()[0];
3067 auto offInterval = style.dashIntervals()[1];
3068 if (offInterval == 0) {
3069 GrStyle strokeStyle(style.strokeRec(), nullptr);
3070 return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
3071 strokeStyle, shaderCaps);
3072 } else if (onInterval == 0) {
3073 // There is nothing to draw but we have no way to indicate that here.
3074 return nullptr;
3075 }
3076 auto angularOnInterval = onInterval / r;
3077 auto angularOffInterval = offInterval / r;
3078 auto phaseAngle = style.dashPhase() / r;
3079 // Currently this function doesn't accept ovals with different start angles, though
3080 // it could.
3081 static const SkScalar kStartAngle = 0.f;
3082 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
3083 style.strokeRec().getWidth(), kStartAngle,
3084 angularOnInterval, angularOffInterval, phaseAngle);
3085 }
3086 return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
3087}
3088
Robert Phillipsb97da532019-02-12 15:24:12 -05003089std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003090 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003091 const SkMatrix& viewMatrix,
3092 const SkRect& oval,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003093 const GrStyle& style,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003094 const GrShaderCaps* shaderCaps) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07003095 // we can draw circles
bsalomon4f3a0ca2016-08-22 13:14:26 -07003096 SkScalar width = oval.width();
Jim Van Verthd952a992017-04-20 17:25:26 -04003097 if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3098 circle_stays_circle(viewMatrix)) {
Jim Van Verth64b85892019-06-17 12:01:46 -04003099 return MakeCircleOp(context, std::move(paint), viewMatrix, oval, style, shaderCaps);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003100 }
3101
3102 if (style.pathEffect()) {
3103 return nullptr;
bsalomon4b4a7cc2016-07-08 04:42:54 -07003104 }
3105
Stan Ilieveb868aa2017-02-21 11:06:16 -05003106 // prefer the device space ellipse op for batchability
bsalomon4b4a7cc2016-07-08 04:42:54 -07003107 if (viewMatrix.rectStaysRect()) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003108 return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
bsalomon4b4a7cc2016-07-08 04:42:54 -07003109 }
3110
Stan Ilieveb868aa2017-02-21 11:06:16 -05003111 // Otherwise, if we have shader derivative support, render as device-independent
3112 if (shaderCaps->shaderDerivativeSupport()) {
Jim Van Vertha925bb02018-09-25 10:49:52 -04003113 SkScalar a = viewMatrix[SkMatrix::kMScaleX];
3114 SkScalar b = viewMatrix[SkMatrix::kMSkewX];
3115 SkScalar c = viewMatrix[SkMatrix::kMSkewY];
3116 SkScalar d = viewMatrix[SkMatrix::kMScaleY];
3117 // Check for near-degenerate matrix
3118 if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003119 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval,
Jim Van Vertha925bb02018-09-25 10:49:52 -04003120 style.strokeRec());
3121 }
Stan Ilieveb868aa2017-02-21 11:06:16 -05003122 }
3123
bsalomon4b4a7cc2016-07-08 04:42:54 -07003124 return nullptr;
3125}
3126
3127///////////////////////////////////////////////////////////////////////////////
joshualitt3e708c52015-04-30 13:49:27 -07003128
Robert Phillipsb97da532019-02-12 15:24:12 -05003129std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003130 GrPaint&& paint,
3131 const SkMatrix& viewMatrix,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003132 const SkRect& oval, SkScalar startAngle,
3133 SkScalar sweepAngle, bool useCenter,
3134 const GrStyle& style,
3135 const GrShaderCaps* shaderCaps) {
bsalomon21af9ca2016-08-25 12:29:23 -07003136 SkASSERT(!oval.isEmpty());
3137 SkASSERT(sweepAngle);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003138 SkScalar width = oval.width();
bsalomon21af9ca2016-08-25 12:29:23 -07003139 if (SkScalarAbs(sweepAngle) >= 360.f) {
3140 return nullptr;
3141 }
bsalomon4f3a0ca2016-08-22 13:14:26 -07003142 if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) {
3143 return nullptr;
3144 }
3145 SkPoint center = {oval.centerX(), oval.centerY()};
Brian Salomon289e3d82016-12-14 15:52:56 -05003146 CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle),
3147 useCenter};
Greg Daniel2655ede2019-04-10 00:49:28 +00003148 return CircleOp::Make(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04003149 center, width / 2.f, style, &arcParams);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003150}
3151
3152///////////////////////////////////////////////////////////////////////////////
3153
Hal Canary6f6961e2017-01-31 13:50:44 -05003154#if GR_TEST_UTILS
joshualitt3e708c52015-04-30 13:49:27 -07003155
Brian Salomon05441c42017-05-15 16:45:49 -04003156GR_DRAW_OP_TEST_DEFINE(CircleOp) {
bsalomon4f3a0ca2016-08-22 13:14:26 -07003157 do {
bsalomoncadf75a2016-08-22 14:24:24 -07003158 SkScalar rotate = random->nextSScalar1() * 360.f;
3159 SkScalar translateX = random->nextSScalar1() * 1000.f;
3160 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003161 SkScalar scale;
3162 do {
3163 scale = random->nextSScalar1() * 100.f;
3164 } while (scale == 0);
bsalomoncadf75a2016-08-22 14:24:24 -07003165 SkMatrix viewMatrix;
3166 viewMatrix.setRotate(rotate);
3167 viewMatrix.postTranslate(translateX, translateY);
3168 viewMatrix.postScale(scale, scale);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003169 SkRect circle = GrTest::TestSquare(random);
3170 SkPoint center = {circle.centerX(), circle.centerY()};
3171 SkScalar radius = circle.width() / 2.f;
3172 SkStrokeRec stroke = GrTest::TestStrokeRec(random);
Brian Salomon289e3d82016-12-14 15:52:56 -05003173 CircleOp::ArcParams arcParamsTmp;
3174 const CircleOp::ArcParams* arcParams = nullptr;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003175 if (random->nextBool()) {
3176 arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2;
robertphillips08197b22016-08-23 06:19:15 -07003177 arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f;
3178 arcParamsTmp.fUseCenter = random->nextBool();
bsalomon4f3a0ca2016-08-22 13:14:26 -07003179 arcParams = &arcParamsTmp;
3180 }
Greg Daniel2655ede2019-04-10 00:49:28 +00003181 std::unique_ptr<GrDrawOp> op = CircleOp::Make(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04003182 center, radius,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003183 GrStyle(stroke, nullptr), arcParams);
Brian Salomon289e3d82016-12-14 15:52:56 -05003184 if (op) {
Brian Salomon5ec9def2016-12-20 15:34:05 -05003185 return op;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003186 }
Mike Klein16885072018-12-11 09:54:31 -05003187 assert_alive(paint);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003188 } while (true);
joshualitt3e708c52015-04-30 13:49:27 -07003189}
3190
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003191GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) {
3192 SkScalar rotate = random->nextSScalar1() * 360.f;
3193 SkScalar translateX = random->nextSScalar1() * 1000.f;
3194 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003195 SkScalar scale;
3196 do {
3197 scale = random->nextSScalar1() * 100.f;
3198 } while (scale == 0);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003199 SkMatrix viewMatrix;
3200 viewMatrix.setRotate(rotate);
3201 viewMatrix.postTranslate(translateX, translateY);
3202 viewMatrix.postScale(scale, scale);
3203 SkRect circle = GrTest::TestSquare(random);
3204 SkPoint center = {circle.centerX(), circle.centerY()};
3205 SkScalar radius = circle.width() / 2.f;
3206 SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius);
3207 SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f);
3208 SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f);
3209 SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f);
3210 SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f);
Robert Phillips7c525e62018-06-12 10:11:12 -04003211 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix,
3212 center, radius, strokeWidth,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003213 startAngle, onAngle, offAngle, phase);
3214}
3215
Brian Salomon05441c42017-05-15 16:45:49 -04003216GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003217 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt6c891102015-05-13 08:51:49 -07003218 SkRect ellipse = GrTest::TestSquare(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003219 return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
Robert Phillips7c525e62018-06-12 10:11:12 -04003220 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003221}
3222
Brian Salomon05441c42017-05-15 16:45:49 -04003223GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003224 SkMatrix viewMatrix = GrTest::TestMatrix(random);
joshualitt6c891102015-05-13 08:51:49 -07003225 SkRect ellipse = GrTest::TestSquare(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003226 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
Robert Phillips7c525e62018-06-12 10:11:12 -04003227 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003228}
3229
Jim Van Verth64b85892019-06-17 12:01:46 -04003230GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) {
3231 do {
3232 SkScalar rotate = random->nextSScalar1() * 360.f;
3233 SkScalar translateX = random->nextSScalar1() * 1000.f;
3234 SkScalar translateY = random->nextSScalar1() * 1000.f;
3235 SkScalar scale;
3236 do {
3237 scale = random->nextSScalar1() * 100.f;
3238 } while (scale == 0);
3239 SkMatrix viewMatrix;
3240 viewMatrix.setRotate(rotate);
3241 viewMatrix.postTranslate(translateX, translateY);
3242 viewMatrix.postScale(scale, scale);
3243 SkRect rect = GrTest::TestRect(random);
3244 SkScalar radius = random->nextRangeF(0.1f, 10.f);
3245 SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius);
3246 if (rrect.isOval()) {
3247 continue;
3248 }
3249 std::unique_ptr<GrDrawOp> op =
3250 GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect,
3251 GrTest::TestStrokeRec(random), nullptr);
3252 if (op) {
3253 return op;
3254 }
3255 assert_alive(paint);
3256 } while (true);
3257}
3258
Brian Salomon05441c42017-05-15 16:45:49 -04003259GR_DRAW_OP_TEST_DEFINE(RRectOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003260 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt3e708c52015-04-30 13:49:27 -07003261 const SkRRect& rrect = GrTest::TestRRectSimple(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003262 return make_rrect_op(context, std::move(paint), viewMatrix, rrect,
Robert Phillips7c525e62018-06-12 10:11:12 -04003263 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003264}
3265
3266#endif