blob: 5afd1d17b8decc6b9d735a94780081148b1d2a6e [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
Brian Osman5ced0bf2019-03-15 10:15:29 -04001224 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
1225 GrFSAAType fsaaType, GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04001226 SkPMColor4f* color = &fCircles.front().fColor;
Brian Osman5ced0bf2019-03-15 10:15:29 -04001227 return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04001228 GrProcessorAnalysisCoverage::kSingleChannel, color,
1229 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001230 }
1231
1232 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1233
bsalomone46f9fe2015-08-18 06:05:14 -07001234private:
Brian Salomon91326c32017-08-09 16:02:19 -04001235 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08001236 SkMatrix localMatrix;
1237 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001238 return;
1239 }
1240
1241 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05001242 sk_sp<GrGeometryProcessor> gp(new CircleGeometryProcessor(
Greg Daniel2655ede2019-04-10 00:49:28 +00001243 !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, fRoundCaps, fWideColor,
1244 localMatrix));
bsalomon4f3a0ca2016-08-22 13:14:26 -07001245
Brian Salomon12d22642019-01-29 14:38:50 -05001246 sk_sp<const GrBuffer> vertexBuffer;
jvanverth6ca48822016-10-07 06:57:32 -07001247 int firstVertex;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001248 GrVertexWriter vertices{target->makeVertexSpace(gp->vertexStride(), fVertCount,
1249 &vertexBuffer, &firstVertex)};
1250 if (!vertices.fPtr) {
jvanverth6ca48822016-10-07 06:57:32 -07001251 SkDebugf("Could not allocate vertices\n");
joshualitt4b31de82015-03-05 14:33:41 -08001252 return;
1253 }
1254
Brian Salomon12d22642019-01-29 14:38:50 -05001255 sk_sp<const GrBuffer> indexBuffer = nullptr;
jvanverth6ca48822016-10-07 06:57:32 -07001256 int firstIndex = 0;
1257 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1258 if (!indices) {
1259 SkDebugf("Could not allocate indices\n");
1260 return;
1261 }
1262
1263 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04001264 for (const auto& circle : fCircles) {
1265 SkScalar innerRadius = circle.fInnerRadius;
1266 SkScalar outerRadius = circle.fOuterRadius;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001267 GrVertexColor color(circle.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001268 const SkRect& bounds = circle.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08001269
joshualitt76e7fb62015-02-11 08:52:27 -08001270 // The inner radius in the vertex data must be specified in normalized space.
1271 innerRadius = innerRadius / outerRadius;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001272 SkPoint radii = { outerRadius, innerRadius };
jvanverth6ca48822016-10-07 06:57:32 -07001273
1274 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
Brian Salomon289e3d82016-12-14 15:52:56 -05001275 SkScalar halfWidth = 0.5f * bounds.width();
Herb Derby60c05f92016-12-13 15:18:55 -05001276
Brian Osman9a24fee2018-08-03 09:48:42 -04001277 SkVector geoClipPlane = { 0, 0 };
1278 SkScalar offsetClipDist = SK_Scalar1;
1279 if (!circle.fStroked && fClipPlane && fClipPlaneIsect &&
1280 (circle.fClipPlane[0] * circle.fIsectPlane[0] +
1281 circle.fClipPlane[1] * circle.fIsectPlane[1]) < 0.0f) {
1282 // Acute arc. Clip the vertices to the perpendicular half-plane. We've constructed
1283 // fClipPlane to be clockwise, and fISectPlane to be CCW, so we can can rotate them
1284 // each 90 degrees to point "out", then average them. We back off by 1/2 pixel so
1285 // the AA can extend just past the center of the circle.
1286 geoClipPlane.set(circle.fClipPlane[1] - circle.fIsectPlane[1],
1287 circle.fIsectPlane[0] - circle.fClipPlane[0]);
1288 SkAssertResult(geoClipPlane.normalize());
1289 offsetClipDist = 0.5f / halfWidth;
1290 }
1291
Brian Osman7d8f82b2018-11-08 10:24:09 -05001292 for (int i = 0; i < 8; ++i) {
1293 // This clips the normalized offset to the half-plane we computed above. Then we
1294 // compute the vertex position from this.
Brian Osman9d958b52018-11-13 12:46:56 -05001295 SkScalar dist = SkTMin(kOctagonOuter[i].dot(geoClipPlane) + offsetClipDist, 0.0f);
1296 SkVector offset = kOctagonOuter[i] - geoClipPlane * dist;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001297 vertices.write(center + offset * halfWidth,
1298 color,
1299 offset,
1300 radii);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001301 if (fClipPlane) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001302 vertices.write(circle.fClipPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001303 }
1304 if (fClipPlaneIsect) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001305 vertices.write(circle.fIsectPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001306 }
1307 if (fClipPlaneUnion) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001308 vertices.write(circle.fUnionPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001309 }
1310 if (fRoundCaps) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001311 vertices.write(circle.fRoundCapCenters);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001312 }
Brian Salomon45c92202018-04-10 10:53:58 -04001313 }
jvanverth6ca48822016-10-07 06:57:32 -07001314
Brian Salomon05441c42017-05-15 16:45:49 -04001315 if (circle.fStroked) {
jvanverth6ca48822016-10-07 06:57:32 -07001316 // compute the inner ring
jvanverth6ca48822016-10-07 06:57:32 -07001317
Brian Osman7d8f82b2018-11-08 10:24:09 -05001318 for (int i = 0; i < 8; ++i) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001319 vertices.write(center + kOctagonInner[i] * circle.fInnerRadius,
1320 color,
1321 kOctagonInner[i] * innerRadius,
1322 radii);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001323 if (fClipPlane) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001324 vertices.write(circle.fClipPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001325 }
1326 if (fClipPlaneIsect) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001327 vertices.write(circle.fIsectPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001328 }
1329 if (fClipPlaneUnion) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001330 vertices.write(circle.fUnionPlane);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001331 }
1332 if (fRoundCaps) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001333 vertices.write(circle.fRoundCapCenters);
Brian Osman7d8f82b2018-11-08 10:24:09 -05001334 }
Brian Salomon45c92202018-04-10 10:53:58 -04001335 }
jvanverth6ca48822016-10-07 06:57:32 -07001336 } else {
1337 // filled
Brian Osmanf9aabff2018-11-13 16:11:38 -05001338 vertices.write(center, color, SkPoint::Make(0, 0), radii);
jvanverth6ca48822016-10-07 06:57:32 -07001339 if (fClipPlane) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001340 vertices.write(circle.fClipPlane);
jvanverth6ca48822016-10-07 06:57:32 -07001341 }
jvanverth6ca48822016-10-07 06:57:32 -07001342 if (fClipPlaneIsect) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001343 vertices.write(circle.fIsectPlane);
jvanverth6ca48822016-10-07 06:57:32 -07001344 }
1345 if (fClipPlaneUnion) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001346 vertices.write(circle.fUnionPlane);
jvanverth6ca48822016-10-07 06:57:32 -07001347 }
Brian Salomon5ceda552018-12-14 16:03:38 -05001348 if (fRoundCaps) {
1349 vertices.write(circle.fRoundCapCenters);
1350 }
jvanverth6ca48822016-10-07 06:57:32 -07001351 }
1352
Brian Salomon05441c42017-05-15 16:45:49 -04001353 const uint16_t* primIndices = circle_type_to_indices(circle.fStroked);
1354 const int primIndexCount = circle_type_to_index_count(circle.fStroked);
jvanverth6ca48822016-10-07 06:57:32 -07001355 for (int i = 0; i < primIndexCount; ++i) {
1356 *indices++ = primIndices[i] + currStartVertex;
1357 }
1358
Brian Salomon05441c42017-05-15 16:45:49 -04001359 currStartVertex += circle_type_to_vert_count(circle.fStroked);
joshualitt76e7fb62015-02-11 08:52:27 -08001360 }
jvanverth6ca48822016-10-07 06:57:32 -07001361
Brian Salomon7eae3e02018-08-07 14:02:38 +00001362 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05001363 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00001364 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05001365 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07001366 target->recordDraw(std::move(gp), mesh);
1367 }
1368
1369 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1370 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08001371 }
1372
Brian Salomon7eae3e02018-08-07 14:02:38 +00001373 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05001374 CircleOp* that = t->cast<CircleOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05001375
1376 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05001377 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001378 return CombineResult::kCannotCombine;
Jim Van Verth8cefe402017-02-09 11:36:37 -05001379 }
1380
Brian Salomon05441c42017-05-15 16:45:49 -04001381 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001382 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07001383 }
1384
Brian Salomon05441c42017-05-15 16:45:49 -04001385 if (fHelper.usesLocalCoords() &&
1386 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001387 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08001388 }
1389
Brian Salomon289e3d82016-12-14 15:52:56 -05001390 // Because we've set up the ops that don't use the planes with noop values
1391 // we can just accumulate used planes by later ops.
bsalomon4f3a0ca2016-08-22 13:14:26 -07001392 fClipPlane |= that->fClipPlane;
1393 fClipPlaneIsect |= that->fClipPlaneIsect;
1394 fClipPlaneUnion |= that->fClipPlaneUnion;
Brian Salomon45c92202018-04-10 10:53:58 -04001395 fRoundCaps |= that->fRoundCaps;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001396 fWideColor |= that->fWideColor;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001397
Brian Salomon05441c42017-05-15 16:45:49 -04001398 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
jvanverth6ca48822016-10-07 06:57:32 -07001399 fVertCount += that->fVertCount;
1400 fIndexCount += that->fIndexCount;
1401 fAllFill = fAllFill && that->fAllFill;
Brian Salomon7eae3e02018-08-07 14:02:38 +00001402 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08001403 }
1404
Brian Salomon05441c42017-05-15 16:45:49 -04001405 struct Circle {
Brian Osmancf860852018-10-31 14:04:39 -04001406 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001407 SkScalar fInnerRadius;
1408 SkScalar fOuterRadius;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001409 SkScalar fClipPlane[3];
1410 SkScalar fIsectPlane[3];
1411 SkScalar fUnionPlane[3];
Brian Salomon45c92202018-04-10 10:53:58 -04001412 SkPoint fRoundCapCenters[2];
Brian Salomon289e3d82016-12-14 15:52:56 -05001413 SkRect fDevBounds;
1414 bool fStroked;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001415 };
1416
Brian Salomon289e3d82016-12-14 15:52:56 -05001417 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04001418 Helper fHelper;
1419 SkSTArray<1, Circle, true> fCircles;
Brian Salomon289e3d82016-12-14 15:52:56 -05001420 int fVertCount;
1421 int fIndexCount;
1422 bool fAllFill;
1423 bool fClipPlane;
1424 bool fClipPlaneIsect;
1425 bool fClipPlaneUnion;
Brian Salomon45c92202018-04-10 10:53:58 -04001426 bool fRoundCaps;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001427 bool fWideColor;
reed1b55a962015-09-17 20:16:13 -07001428
Brian Salomon05441c42017-05-15 16:45:49 -04001429 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08001430};
1431
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001432class ButtCapDashedCircleOp final : public GrMeshDrawOp {
1433private:
1434 using Helper = GrSimpleMeshDrawOpHelper;
1435
1436public:
1437 DEFINE_OP_CLASS_ID
1438
Robert Phillipsb97da532019-02-12 15:24:12 -05001439 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04001440 GrPaint&& paint,
1441 const SkMatrix& viewMatrix,
1442 SkPoint center,
1443 SkScalar radius,
1444 SkScalar strokeWidth,
1445 SkScalar startAngle,
1446 SkScalar onAngle,
1447 SkScalar offAngle,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001448 SkScalar phaseAngle) {
1449 SkASSERT(circle_stays_circle(viewMatrix));
1450 SkASSERT(strokeWidth < 2 * radius);
Robert Phillips7c525e62018-06-12 10:11:12 -04001451 return Helper::FactoryHelper<ButtCapDashedCircleOp>(context, std::move(paint), viewMatrix,
1452 center, radius, strokeWidth, startAngle,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001453 onAngle, offAngle, phaseAngle);
1454 }
1455
Brian Osmancf860852018-10-31 14:04:39 -04001456 ButtCapDashedCircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001457 const SkMatrix& viewMatrix, SkPoint center, SkScalar radius,
1458 SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle,
1459 SkScalar offAngle, SkScalar phaseAngle)
1460 : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
1461 SkASSERT(circle_stays_circle(viewMatrix));
1462 viewMatrix.mapPoints(&center, 1);
1463 radius = viewMatrix.mapRadius(radius);
1464 strokeWidth = viewMatrix.mapRadius(strokeWidth);
1465
1466 // Determine the angle where the circle starts in device space and whether its orientation
1467 // has been reversed.
1468 SkVector start;
1469 bool reflection;
1470 if (!startAngle) {
1471 start = {1, 0};
1472 } else {
Brian Osman4428f2c2019-04-02 10:59:28 -04001473 start.fY = SkScalarSin(startAngle);
1474 start.fX = SkScalarCos(startAngle);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001475 }
1476 viewMatrix.mapVectors(&start, 1);
1477 startAngle = SkScalarATan2(start.fY, start.fX);
1478 reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() -
1479 viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0;
1480
1481 auto totalAngle = onAngle + offAngle;
1482 phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2;
1483
1484 SkScalar halfWidth = 0;
1485 if (SkScalarNearlyZero(strokeWidth)) {
1486 halfWidth = SK_ScalarHalf;
1487 } else {
1488 halfWidth = SkScalarHalf(strokeWidth);
1489 }
1490
1491 SkScalar outerRadius = radius + halfWidth;
1492 SkScalar innerRadius = radius - halfWidth;
1493
1494 // The radii are outset for two reasons. First, it allows the shader to simply perform
1495 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
1496 // Second, the outer radius is used to compute the verts of the bounding box that is
1497 // rendered and the outset ensures the box will cover all partially covered by the circle.
1498 outerRadius += SK_ScalarHalf;
1499 innerRadius -= SK_ScalarHalf;
1500 fViewMatrixIfUsingLocalCoords = viewMatrix;
1501
1502 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
1503 center.fX + outerRadius, center.fY + outerRadius);
1504
1505 // We store whether there is a reflection as a negative total angle.
1506 if (reflection) {
1507 totalAngle = -totalAngle;
1508 }
1509 fCircles.push_back(Circle{
1510 color,
1511 outerRadius,
1512 innerRadius,
1513 onAngle,
1514 totalAngle,
1515 startAngle,
1516 phaseAngle,
1517 devBounds
1518 });
1519 // Use the original radius and stroke radius for the bounds so that it does not include the
1520 // AA bloat.
1521 radius += halfWidth;
1522 this->setBounds(
1523 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
1524 HasAABloat::kYes, IsZeroArea::kNo);
1525 fVertCount = circle_type_to_vert_count(true);
1526 fIndexCount = circle_type_to_index_count(true);
1527 }
1528
1529 const char* name() const override { return "ButtCappedDashedCircleOp"; }
1530
Chris Dalton1706cbf2019-05-21 19:35:29 -06001531 void visitProxies(const VisitProxyFunc& func) const override {
Brian Salomon7d94bb52018-10-12 14:37:19 -04001532 fHelper.visitProxies(func);
1533 }
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001534
Brian Osman9a390ac2018-11-12 09:47:48 -05001535#ifdef SK_DEBUG
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001536 SkString dumpInfo() const override {
1537 SkString string;
1538 for (int i = 0; i < fCircles.count(); ++i) {
1539 string.appendf(
1540 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1541 "InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, "
1542 "Phase: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04001543 fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft,
Brian Osman1be2b7c2018-10-29 16:07:15 -04001544 fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight,
1545 fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius,
1546 fCircles[i].fOuterRadius, fCircles[i].fOnAngle, fCircles[i].fTotalAngle,
1547 fCircles[i].fPhaseAngle);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001548 }
1549 string += fHelper.dumpInfo();
1550 string += INHERITED::dumpInfo();
1551 return string;
1552 }
Brian Osman9a390ac2018-11-12 09:47:48 -05001553#endif
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001554
Brian Osman5ced0bf2019-03-15 10:15:29 -04001555 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
1556 GrFSAAType fsaaType, GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04001557 SkPMColor4f* color = &fCircles.front().fColor;
Brian Osman5ced0bf2019-03-15 10:15:29 -04001558 return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04001559 GrProcessorAnalysisCoverage::kSingleChannel, color,
1560 &fWideColor);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001561 }
1562
1563 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1564
1565private:
1566 void onPrepareDraws(Target* target) override {
1567 SkMatrix localMatrix;
1568 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
1569 return;
1570 }
1571
1572 // Setup geometry processor
Brian Osmane3caf2d2018-11-21 13:48:36 -05001573 sk_sp<GrGeometryProcessor> gp(new ButtCapDashedCircleGeometryProcessor(fWideColor,
1574 localMatrix));
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001575
Brian Salomon12d22642019-01-29 14:38:50 -05001576 sk_sp<const GrBuffer> vertexBuffer;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001577 int firstVertex;
Brian Osmanf9aabff2018-11-13 16:11:38 -05001578 GrVertexWriter vertices{target->makeVertexSpace(gp->vertexStride(), fVertCount,
1579 &vertexBuffer, &firstVertex)};
1580 if (!vertices.fPtr) {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001581 SkDebugf("Could not allocate vertices\n");
1582 return;
1583 }
1584
Brian Salomon12d22642019-01-29 14:38:50 -05001585 sk_sp<const GrBuffer> indexBuffer;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001586 int firstIndex = 0;
1587 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1588 if (!indices) {
1589 SkDebugf("Could not allocate indices\n");
1590 return;
1591 }
1592
1593 int currStartVertex = 0;
1594 for (const auto& circle : fCircles) {
1595 // The inner radius in the vertex data must be specified in normalized space so that
1596 // length() can be called with smaller values to avoid precision issues with half
1597 // floats.
1598 auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius;
1599 const SkRect& bounds = circle.fDevBounds;
1600 bool reflect = false;
Brian Osman9d958b52018-11-13 12:46:56 -05001601 struct { float onAngle, totalAngle, startAngle, phaseAngle; } dashParams = {
1602 circle.fOnAngle, circle.fTotalAngle, circle.fStartAngle, circle.fPhaseAngle
1603 };
1604 if (dashParams.totalAngle < 0) {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001605 reflect = true;
Brian Osman9d958b52018-11-13 12:46:56 -05001606 dashParams.totalAngle = -dashParams.totalAngle;
1607 dashParams.startAngle = -dashParams.startAngle;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001608 }
1609
Brian Osmane3caf2d2018-11-21 13:48:36 -05001610 GrVertexColor color(circle.fColor, fWideColor);
Brian Osman1be2b7c2018-10-29 16:07:15 -04001611
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001612 // The bounding geometry for the circle is composed of an outer bounding octagon and
1613 // an inner bounded octagon.
1614
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001615 // Compute the vertices of the outer octagon.
1616 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
1617 SkScalar halfWidth = 0.5f * bounds.width();
Brian Osman9d958b52018-11-13 12:46:56 -05001618
1619 auto reflectY = [=](const SkPoint& p) {
1620 return SkPoint{ p.fX, reflect ? -p.fY : p.fY };
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001621 };
Brian Osman9d958b52018-11-13 12:46:56 -05001622
1623 for (int i = 0; i < 8; ++i) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001624 vertices.write(center + kOctagonOuter[i] * halfWidth,
1625 color,
1626 reflectY(kOctagonOuter[i]),
1627 circle.fOuterRadius,
1628 normInnerRadius,
1629 dashParams);
Brian Osman9d958b52018-11-13 12:46:56 -05001630 }
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001631
1632 // Compute the vertices of the inner octagon.
Brian Osman9d958b52018-11-13 12:46:56 -05001633 for (int i = 0; i < 8; ++i) {
Brian Osmanf9aabff2018-11-13 16:11:38 -05001634 vertices.write(center + kOctagonInner[i] * circle.fInnerRadius,
1635 color,
1636 reflectY(kOctagonInner[i]) * normInnerRadius,
1637 circle.fOuterRadius,
1638 normInnerRadius,
1639 dashParams);
Brian Osman9d958b52018-11-13 12:46:56 -05001640 }
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001641
1642 const uint16_t* primIndices = circle_type_to_indices(true);
1643 const int primIndexCount = circle_type_to_index_count(true);
1644 for (int i = 0; i < primIndexCount; ++i) {
1645 *indices++ = primIndices[i] + currStartVertex;
1646 }
1647
1648 currStartVertex += circle_type_to_vert_count(true);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001649 }
1650
Brian Salomon7eae3e02018-08-07 14:02:38 +00001651 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05001652 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00001653 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05001654 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07001655 target->recordDraw(std::move(gp), mesh);
1656 }
1657
1658 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1659 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001660 }
1661
Brian Salomon7eae3e02018-08-07 14:02:38 +00001662 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001663 ButtCapDashedCircleOp* that = t->cast<ButtCapDashedCircleOp>();
1664
1665 // can only represent 65535 unique vertices with 16-bit indices
1666 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001667 return CombineResult::kCannotCombine;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001668 }
1669
1670 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001671 return CombineResult::kCannotCombine;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001672 }
1673
1674 if (fHelper.usesLocalCoords() &&
1675 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001676 return CombineResult::kCannotCombine;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001677 }
1678
1679 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001680 fVertCount += that->fVertCount;
1681 fIndexCount += that->fIndexCount;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001682 fWideColor |= that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00001683 return CombineResult::kMerged;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001684 }
1685
1686 struct Circle {
Brian Osmancf860852018-10-31 14:04:39 -04001687 SkPMColor4f fColor;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001688 SkScalar fOuterRadius;
1689 SkScalar fInnerRadius;
1690 SkScalar fOnAngle;
1691 SkScalar fTotalAngle;
1692 SkScalar fStartAngle;
1693 SkScalar fPhaseAngle;
1694 SkRect fDevBounds;
1695 };
1696
1697 SkMatrix fViewMatrixIfUsingLocalCoords;
1698 Helper fHelper;
1699 SkSTArray<1, Circle, true> fCircles;
1700 int fVertCount;
1701 int fIndexCount;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001702 bool fWideColor;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001703
1704 typedef GrMeshDrawOp INHERITED;
1705};
1706
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00001707///////////////////////////////////////////////////////////////////////////////
1708
Brian Salomon05441c42017-05-15 16:45:49 -04001709class EllipseOp : public GrMeshDrawOp {
1710private:
1711 using Helper = GrSimpleMeshDrawOpHelper;
1712
1713 struct DeviceSpaceParams {
1714 SkPoint fCenter;
1715 SkScalar fXRadius;
1716 SkScalar fYRadius;
1717 SkScalar fInnerXRadius;
1718 SkScalar fInnerYRadius;
1719 };
1720
joshualitt76e7fb62015-02-11 08:52:27 -08001721public:
Brian Salomon25a88092016-12-01 09:36:50 -05001722 DEFINE_OP_CLASS_ID
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001723
Robert Phillipsb97da532019-02-12 15:24:12 -05001724 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04001725 GrPaint&& paint,
1726 const SkMatrix& viewMatrix,
1727 const SkRect& ellipse,
1728 const SkStrokeRec& stroke) {
Brian Salomon05441c42017-05-15 16:45:49 -04001729 DeviceSpaceParams params;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001730 // do any matrix crunching before we reset the draw state for device coords
Brian Salomon05441c42017-05-15 16:45:49 -04001731 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
1732 viewMatrix.mapPoints(&params.fCenter, 1);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001733 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width());
1734 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height());
Brian Salomon05441c42017-05-15 16:45:49 -04001735 params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius +
1736 viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius);
1737 params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius +
1738 viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08001739
bsalomon4b4a7cc2016-07-08 04:42:54 -07001740 // do (potentially) anisotropic mapping of stroke
1741 SkVector scaledStroke;
1742 SkScalar strokeWidth = stroke.getWidth();
Brian Salomon289e3d82016-12-14 15:52:56 -05001743 scaledStroke.fX = SkScalarAbs(
1744 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
1745 scaledStroke.fY = SkScalarAbs(
1746 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
bsalomon4b4a7cc2016-07-08 04:42:54 -07001747
1748 SkStrokeRec::Style style = stroke.getStyle();
Brian Salomon289e3d82016-12-14 15:52:56 -05001749 bool isStrokeOnly =
1750 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001751 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
1752
Brian Salomon05441c42017-05-15 16:45:49 -04001753 params.fInnerXRadius = 0;
1754 params.fInnerYRadius = 0;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001755 if (hasStroke) {
1756 if (SkScalarNearlyZero(scaledStroke.length())) {
1757 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
1758 } else {
1759 scaledStroke.scale(SK_ScalarHalf);
1760 }
1761
1762 // we only handle thick strokes for near-circular ellipses
1763 if (scaledStroke.length() > SK_ScalarHalf &&
Brian Salomon05441c42017-05-15 16:45:49 -04001764 (0.5f * params.fXRadius > params.fYRadius ||
1765 0.5f * params.fYRadius > params.fXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07001766 return nullptr;
1767 }
1768
1769 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon05441c42017-05-15 16:45:49 -04001770 if (scaledStroke.fX * (params.fXRadius * params.fYRadius) <
1771 (scaledStroke.fY * scaledStroke.fY) * params.fXRadius ||
1772 scaledStroke.fY * (params.fXRadius * params.fXRadius) <
1773 (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07001774 return nullptr;
1775 }
1776
1777 // this is legit only if scale & translation (which should be the case at the moment)
1778 if (isStrokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04001779 params.fInnerXRadius = params.fXRadius - scaledStroke.fX;
1780 params.fInnerYRadius = params.fYRadius - scaledStroke.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001781 }
1782
Brian Salomon05441c42017-05-15 16:45:49 -04001783 params.fXRadius += scaledStroke.fX;
1784 params.fYRadius += scaledStroke.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001785 }
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001786
1787 // For large ovals with low precision floats, we fall back to the path renderer.
1788 // To compute the AA at the edge we divide by the gradient, which is clamped to a
1789 // minimum value to avoid divides by zero. With large ovals and low precision this
1790 // leads to blurring at the edge of the oval.
1791 const SkScalar kMaxOvalRadius = 16384;
1792 if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
1793 (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
1794 return nullptr;
1795 }
1796
Greg Daniel2655ede2019-04-10 00:49:28 +00001797 return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04001798 params, stroke);
Brian Salomon05441c42017-05-15 16:45:49 -04001799 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07001800
Brian Osmancf860852018-10-31 14:04:39 -04001801 EllipseOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Greg Daniel2655ede2019-04-10 00:49:28 +00001802 const SkMatrix& viewMatrix, const DeviceSpaceParams& params,
Brian Osman936fe7d2018-10-30 15:30:35 -04001803 const SkStrokeRec& stroke)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001804 : INHERITED(ClassID())
1805 , fHelper(helperArgs, GrAAType::kCoverage)
1806 , fUseScale(false) {
Brian Salomon05441c42017-05-15 16:45:49 -04001807 SkStrokeRec::Style style = stroke.getStyle();
1808 bool isStrokeOnly =
1809 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001810
Brian Salomon05441c42017-05-15 16:45:49 -04001811 fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius,
1812 params.fInnerXRadius, params.fInnerYRadius,
1813 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius,
1814 params.fCenter.fY - params.fYRadius,
1815 params.fCenter.fX + params.fXRadius,
1816 params.fCenter.fY + params.fYRadius)});
1817
Greg Daniel2655ede2019-04-10 00:49:28 +00001818 this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07001819
bsalomon4b4a7cc2016-07-08 04:42:54 -07001820 // Outset bounds to include half-pixel width antialiasing.
Greg Daniel2655ede2019-04-10 00:49:28 +00001821 fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001822
Brian Salomon05441c42017-05-15 16:45:49 -04001823 fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0;
1824 fViewMatrixIfUsingLocalCoords = viewMatrix;
bsalomoncdaa97b2016-03-08 08:30:14 -08001825 }
joshualitt76e7fb62015-02-11 08:52:27 -08001826
Brian Salomon289e3d82016-12-14 15:52:56 -05001827 const char* name() const override { return "EllipseOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08001828
Chris Dalton1706cbf2019-05-21 19:35:29 -06001829 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001830 fHelper.visitProxies(func);
1831 }
1832
Brian Osman9a390ac2018-11-12 09:47:48 -05001833#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05001834 SkString dumpInfo() const override {
1835 SkString string;
1836 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04001837 for (const auto& geo : fEllipses) {
Brian Salomon289e3d82016-12-14 15:52:56 -05001838 string.appendf(
1839 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
1840 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04001841 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04001842 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
1843 geo.fInnerXRadius, geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05001844 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04001845 string += fHelper.dumpInfo();
1846 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05001847 return string;
1848 }
Brian Osman9a390ac2018-11-12 09:47:48 -05001849#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05001850
Brian Osman5ced0bf2019-03-15 10:15:29 -04001851 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
1852 GrFSAAType fsaaType, GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001853 fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
1854 !caps.shaderCaps()->hasLowFragmentPrecision();
Brian Osmancf860852018-10-31 14:04:39 -04001855 SkPMColor4f* color = &fEllipses.front().fColor;
Brian Osman5ced0bf2019-03-15 10:15:29 -04001856 return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04001857 GrProcessorAnalysisCoverage::kSingleChannel, color,
1858 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001859 }
1860
1861 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1862
bsalomone46f9fe2015-08-18 06:05:14 -07001863private:
Brian Salomon91326c32017-08-09 16:02:19 -04001864 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08001865 SkMatrix localMatrix;
1866 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001867 return;
1868 }
1869
1870 // Setup geometry processor
Greg Daniel2655ede2019-04-10 00:49:28 +00001871 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
1872 localMatrix));
Brian Osman9d958b52018-11-13 12:46:56 -05001873 QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
Brian Osmanf9aabff2018-11-13 16:11:38 -05001874 GrVertexWriter verts{helper.vertices()};
1875 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08001876 return;
1877 }
1878
Brian Salomon05441c42017-05-15 16:45:49 -04001879 for (const auto& ellipse : fEllipses) {
Brian Osmane3caf2d2018-11-21 13:48:36 -05001880 GrVertexColor color(ellipse.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04001881 SkScalar xRadius = ellipse.fXRadius;
1882 SkScalar yRadius = ellipse.fYRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001883
1884 // Compute the reciprocals of the radii here to save time in the shader
Brian Osman9d958b52018-11-13 12:46:56 -05001885 struct { float xOuter, yOuter, xInner, yInner; } invRadii = {
1886 SkScalarInvert(xRadius),
1887 SkScalarInvert(yRadius),
1888 SkScalarInvert(ellipse.fInnerXRadius),
1889 SkScalarInvert(ellipse.fInnerYRadius)
1890 };
Greg Daniel2655ede2019-04-10 00:49:28 +00001891 SkScalar xMaxOffset = xRadius + SK_ScalarHalf;
1892 SkScalar yMaxOffset = yRadius + SK_ScalarHalf;
vjiaoblack977996d2016-06-30 12:20:54 -07001893
Jim Van Verthb1b87d92018-06-28 10:46:05 -04001894 if (!fStroked) {
1895 // For filled ellipses we map a unit circle in the vertex attributes rather than
1896 // computing an ellipse and modifying that distance, so we normalize to 1
1897 xMaxOffset /= xRadius;
1898 yMaxOffset /= yRadius;
1899 }
1900
joshualitt76e7fb62015-02-11 08:52:27 -08001901 // The inner radius in the vertex data must be specified in normalized space.
Brian Osman2b6e3902018-11-21 15:29:43 -05001902 verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fDevBounds),
1903 color,
1904 origin_centered_tri_strip(xMaxOffset, yMaxOffset),
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001905 GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)),
Brian Osman2b6e3902018-11-21 15:29:43 -05001906 invRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08001907 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07001908 helper.recordDraw(target, std::move(gp));
1909 }
1910
1911 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
1912 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08001913 }
1914
Brian Salomon7eae3e02018-08-07 14:02:38 +00001915 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05001916 EllipseOp* that = t->cast<EllipseOp>();
bsalomonabd30f52015-08-13 13:34:48 -07001917
Brian Salomon05441c42017-05-15 16:45:49 -04001918 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001919 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07001920 }
1921
bsalomoncdaa97b2016-03-08 08:30:14 -08001922 if (fStroked != that->fStroked) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001923 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08001924 }
1925
Brian Salomon05441c42017-05-15 16:45:49 -04001926 if (fHelper.usesLocalCoords() &&
1927 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00001928 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08001929 }
1930
Brian Salomon05441c42017-05-15 16:45:49 -04001931 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
Brian Osmane3caf2d2018-11-21 13:48:36 -05001932 fWideColor |= that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00001933 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08001934 }
1935
Brian Salomon05441c42017-05-15 16:45:49 -04001936 struct Ellipse {
Brian Osmancf860852018-10-31 14:04:39 -04001937 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001938 SkScalar fXRadius;
1939 SkScalar fYRadius;
1940 SkScalar fInnerXRadius;
1941 SkScalar fInnerYRadius;
1942 SkRect fDevBounds;
1943 };
joshualitt76e7fb62015-02-11 08:52:27 -08001944
Brian Salomon289e3d82016-12-14 15:52:56 -05001945 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04001946 Helper fHelper;
1947 bool fStroked;
Brian Osmane3caf2d2018-11-21 13:48:36 -05001948 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04001949 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04001950 SkSTArray<1, Ellipse, true> fEllipses;
reed1b55a962015-09-17 20:16:13 -07001951
Brian Salomon05441c42017-05-15 16:45:49 -04001952 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08001953};
1954
joshualitt76e7fb62015-02-11 08:52:27 -08001955/////////////////////////////////////////////////////////////////////////////////////////////////
1956
Brian Salomon05441c42017-05-15 16:45:49 -04001957class DIEllipseOp : public GrMeshDrawOp {
1958private:
1959 using Helper = GrSimpleMeshDrawOpHelper;
1960
1961 struct DeviceSpaceParams {
1962 SkPoint fCenter;
1963 SkScalar fXRadius;
1964 SkScalar fYRadius;
1965 SkScalar fInnerXRadius;
1966 SkScalar fInnerYRadius;
1967 DIEllipseStyle fStyle;
1968 };
1969
joshualitt76e7fb62015-02-11 08:52:27 -08001970public:
Brian Salomon25a88092016-12-01 09:36:50 -05001971 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07001972
Robert Phillipsb97da532019-02-12 15:24:12 -05001973 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04001974 GrPaint&& paint,
1975 const SkMatrix& viewMatrix,
1976 const SkRect& ellipse,
1977 const SkStrokeRec& stroke) {
Brian Salomon05441c42017-05-15 16:45:49 -04001978 DeviceSpaceParams params;
1979 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
1980 params.fXRadius = SkScalarHalf(ellipse.width());
1981 params.fYRadius = SkScalarHalf(ellipse.height());
joshualitt76e7fb62015-02-11 08:52:27 -08001982
bsalomon4b4a7cc2016-07-08 04:42:54 -07001983 SkStrokeRec::Style style = stroke.getStyle();
Brian Salomon05441c42017-05-15 16:45:49 -04001984 params.fStyle = (SkStrokeRec::kStroke_Style == style)
1985 ? DIEllipseStyle::kStroke
1986 : (SkStrokeRec::kHairline_Style == style)
1987 ? DIEllipseStyle::kHairline
1988 : DIEllipseStyle::kFill;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001989
Brian Salomon05441c42017-05-15 16:45:49 -04001990 params.fInnerXRadius = 0;
1991 params.fInnerYRadius = 0;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001992 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) {
1993 SkScalar strokeWidth = stroke.getWidth();
1994
1995 if (SkScalarNearlyZero(strokeWidth)) {
1996 strokeWidth = SK_ScalarHalf;
1997 } else {
1998 strokeWidth *= SK_ScalarHalf;
1999 }
2000
2001 // we only handle thick strokes for near-circular ellipses
2002 if (strokeWidth > SK_ScalarHalf &&
Brian Salomon05441c42017-05-15 16:45:49 -04002003 (SK_ScalarHalf * params.fXRadius > params.fYRadius ||
2004 SK_ScalarHalf * params.fYRadius > params.fXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002005 return nullptr;
2006 }
2007
2008 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon05441c42017-05-15 16:45:49 -04002009 if (strokeWidth * (params.fYRadius * params.fYRadius) <
2010 (strokeWidth * strokeWidth) * params.fXRadius) {
2011 return nullptr;
2012 }
2013 if (strokeWidth * (params.fXRadius * params.fXRadius) <
2014 (strokeWidth * strokeWidth) * params.fYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002015 return nullptr;
2016 }
2017
2018 // set inner radius (if needed)
2019 if (SkStrokeRec::kStroke_Style == style) {
Brian Salomon05441c42017-05-15 16:45:49 -04002020 params.fInnerXRadius = params.fXRadius - strokeWidth;
2021 params.fInnerYRadius = params.fYRadius - strokeWidth;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002022 }
2023
Brian Salomon05441c42017-05-15 16:45:49 -04002024 params.fXRadius += strokeWidth;
2025 params.fYRadius += strokeWidth;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002026 }
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002027
2028 // For large ovals with low precision floats, we fall back to the path renderer.
2029 // To compute the AA at the edge we divide by the gradient, which is clamped to a
2030 // minimum value to avoid divides by zero. With large ovals and low precision this
2031 // leads to blurring at the edge of the oval.
2032 const SkScalar kMaxOvalRadius = 16384;
2033 if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
2034 (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
2035 return nullptr;
2036 }
2037
Brian Salomon05441c42017-05-15 16:45:49 -04002038 if (DIEllipseStyle::kStroke == params.fStyle &&
2039 (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) {
2040 params.fStyle = DIEllipseStyle::kFill;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002041 }
Greg Daniel2655ede2019-04-10 00:49:28 +00002042 return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), params, viewMatrix);
Brian Salomon05441c42017-05-15 16:45:49 -04002043 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002044
Greg Daniel2655ede2019-04-10 00:49:28 +00002045 DIEllipseOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002046 const DeviceSpaceParams& params, const SkMatrix& viewMatrix)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002047 : INHERITED(ClassID())
2048 , fHelper(helperArgs, GrAAType::kCoverage)
2049 , fUseScale(false) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002050 // This expands the outer rect so that after CTM we end up with a half-pixel border
2051 SkScalar a = viewMatrix[SkMatrix::kMScaleX];
2052 SkScalar b = viewMatrix[SkMatrix::kMSkewX];
2053 SkScalar c = viewMatrix[SkMatrix::kMSkewY];
2054 SkScalar d = viewMatrix[SkMatrix::kMScaleY];
2055 SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c);
2056 SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002057
Brian Salomon05441c42017-05-15 16:45:49 -04002058 fEllipses.emplace_back(
2059 Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius,
2060 params.fInnerYRadius, geoDx, geoDy, params.fStyle,
2061 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius - geoDx,
2062 params.fCenter.fY - params.fYRadius - geoDy,
2063 params.fCenter.fX + params.fXRadius + geoDx,
2064 params.fCenter.fY + params.fYRadius + geoDy)});
Greg Daniel2655ede2019-04-10 00:49:28 +00002065 this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes,
Brian Salomon05441c42017-05-15 16:45:49 -04002066 IsZeroArea::kNo);
joshualitt76e7fb62015-02-11 08:52:27 -08002067 }
2068
Brian Salomon289e3d82016-12-14 15:52:56 -05002069 const char* name() const override { return "DIEllipseOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002070
Chris Dalton1706cbf2019-05-21 19:35:29 -06002071 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002072 fHelper.visitProxies(func);
2073 }
2074
Brian Osman9a390ac2018-11-12 09:47:48 -05002075#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05002076 SkString dumpInfo() const override {
2077 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002078 for (const auto& geo : fEllipses) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002079 string.appendf(
2080 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, "
2081 "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, "
2082 "GeoDY: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002083 geo.fColor.toBytes_RGBA(), geo.fBounds.fLeft, geo.fBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002084 geo.fBounds.fRight, geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius,
2085 geo.fInnerXRadius, geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002086 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002087 string += fHelper.dumpInfo();
2088 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002089 return string;
2090 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002091#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05002092
Brian Osman5ced0bf2019-03-15 10:15:29 -04002093 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
2094 GrFSAAType fsaaType, GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002095 fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
2096 !caps.shaderCaps()->hasLowFragmentPrecision();
Brian Osmancf860852018-10-31 14:04:39 -04002097 SkPMColor4f* color = &fEllipses.front().fColor;
Brian Osman5ced0bf2019-03-15 10:15:29 -04002098 return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002099 GrProcessorAnalysisCoverage::kSingleChannel, color,
2100 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002101 }
2102
2103 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2104
bsalomone46f9fe2015-08-18 06:05:14 -07002105private:
Brian Salomon91326c32017-08-09 16:02:19 -04002106 void onPrepareDraws(Target* target) override {
joshualitt76e7fb62015-02-11 08:52:27 -08002107 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002108 sk_sp<GrGeometryProcessor> gp(
Greg Daniel2655ede2019-04-10 00:49:28 +00002109 new DIEllipseGeometryProcessor(fWideColor, fUseScale, this->viewMatrix(),
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002110 this->style()));
joshualitt76e7fb62015-02-11 08:52:27 -08002111
Brian Osman9d958b52018-11-13 12:46:56 -05002112 QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
Brian Osmanf9aabff2018-11-13 16:11:38 -05002113 GrVertexWriter verts{helper.vertices()};
2114 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002115 return;
2116 }
2117
Brian Salomon05441c42017-05-15 16:45:49 -04002118 for (const auto& ellipse : fEllipses) {
Brian Osmane3caf2d2018-11-21 13:48:36 -05002119 GrVertexColor color(ellipse.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002120 SkScalar xRadius = ellipse.fXRadius;
2121 SkScalar yRadius = ellipse.fYRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002122
joshualitt76e7fb62015-02-11 08:52:27 -08002123 // This adjusts the "radius" to include the half-pixel border
Brian Salomon05441c42017-05-15 16:45:49 -04002124 SkScalar offsetDx = ellipse.fGeoDx / xRadius;
2125 SkScalar offsetDy = ellipse.fGeoDy / yRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002126
Brian Osman9d958b52018-11-13 12:46:56 -05002127 // By default, constructed so that inner offset is (0, 0) for all points
2128 SkScalar innerRatioX = -offsetDx;
2129 SkScalar innerRatioY = -offsetDy;
joshualitt76e7fb62015-02-11 08:52:27 -08002130
Brian Osman9d958b52018-11-13 12:46:56 -05002131 // ... unless we're stroked
Greg Daniel75a13022018-04-04 08:59:20 -04002132 if (DIEllipseStyle::kStroke == this->style()) {
Brian Osman9d958b52018-11-13 12:46:56 -05002133 innerRatioX = xRadius / ellipse.fInnerXRadius;
2134 innerRatioY = yRadius / ellipse.fInnerYRadius;
Greg Daniel75a13022018-04-04 08:59:20 -04002135 }
joshualitt76e7fb62015-02-11 08:52:27 -08002136
Brian Osman2b6e3902018-11-21 15:29:43 -05002137 verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fBounds),
2138 color,
2139 origin_centered_tri_strip(1.0f + offsetDx, 1.0f + offsetDy),
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002140 GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)),
Brian Osman2b6e3902018-11-21 15:29:43 -05002141 origin_centered_tri_strip(innerRatioX + offsetDx,
2142 innerRatioY + offsetDy));
joshualitt76e7fb62015-02-11 08:52:27 -08002143 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002144 helper.recordDraw(target, std::move(gp));
2145 }
2146
2147 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2148 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002149 }
halcanary9d524f22016-03-29 09:03:52 -07002150
Brian Salomon7eae3e02018-08-07 14:02:38 +00002151 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002152 DIEllipseOp* that = t->cast<DIEllipseOp>();
Brian Salomon05441c42017-05-15 16:45:49 -04002153 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002154 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002155 }
2156
bsalomoncdaa97b2016-03-08 08:30:14 -08002157 if (this->style() != that->style()) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002158 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002159 }
2160
joshualittd96a67b2015-05-05 14:09:05 -07002161 // TODO rewrite to allow positioning on CPU
2162 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002163 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002164 }
2165
Brian Salomon05441c42017-05-15 16:45:49 -04002166 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
Brian Osmane3caf2d2018-11-21 13:48:36 -05002167 fWideColor |= that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002168 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002169 }
2170
Brian Salomon05441c42017-05-15 16:45:49 -04002171 const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; }
2172 DIEllipseStyle style() const { return fEllipses[0].fStyle; }
joshualitt76e7fb62015-02-11 08:52:27 -08002173
Brian Salomon05441c42017-05-15 16:45:49 -04002174 struct Ellipse {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002175 SkMatrix fViewMatrix;
Brian Osmancf860852018-10-31 14:04:39 -04002176 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002177 SkScalar fXRadius;
2178 SkScalar fYRadius;
2179 SkScalar fInnerXRadius;
2180 SkScalar fInnerYRadius;
2181 SkScalar fGeoDx;
2182 SkScalar fGeoDy;
2183 DIEllipseStyle fStyle;
2184 SkRect fBounds;
2185 };
2186
Brian Salomon05441c42017-05-15 16:45:49 -04002187 Helper fHelper;
Brian Osmane3caf2d2018-11-21 13:48:36 -05002188 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002189 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04002190 SkSTArray<1, Ellipse, true> fEllipses;
reed1b55a962015-09-17 20:16:13 -07002191
Brian Salomon05441c42017-05-15 16:45:49 -04002192 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002193};
2194
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002195///////////////////////////////////////////////////////////////////////////////
2196
Mike Kleinfc6c37b2016-09-27 09:34:10 -04002197// We have three possible cases for geometry for a roundrect.
jvanverthc3d0e422016-08-25 08:12:35 -07002198//
2199// In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch:
2200// ____________
2201// |_|________|_|
2202// | | | |
2203// | | | |
2204// | | | |
2205// |_|________|_|
2206// |_|________|_|
2207//
2208// For strokes, we don't draw the center quad.
2209//
2210// For circular roundrects, in the case where the stroke width is greater than twice
2211// the corner radius (overstroke), we add additional geometry to mark out the rectangle
jvanverth6a397612016-08-26 08:15:33 -07002212// in the center. The shared vertices are duplicated so we can set a different outer radius
2213// for the fill calculation.
jvanverthc3d0e422016-08-25 08:12:35 -07002214// ____________
2215// |_|________|_|
2216// | |\ ____ /| |
2217// | | | | | |
2218// | | |____| | |
2219// |_|/______\|_|
2220// |_|________|_|
2221//
2222// We don't draw the center quad from the fill rect in this case.
Robert Phillips79839d42016-10-06 15:03:34 -04002223//
2224// For filled rrects that need to provide a distance vector we resuse the overstroke
2225// geometry but make the inner rect degenerate (either a point or a horizontal or
2226// vertical line).
jvanverthc3d0e422016-08-25 08:12:35 -07002227
jvanverth84839f62016-08-29 10:16:40 -07002228static const uint16_t gOverstrokeRRectIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -05002229 // clang-format off
2230 // overstroke quads
2231 // we place this at the beginning so that we can skip these indices when rendering normally
2232 16, 17, 19, 16, 19, 18,
2233 19, 17, 23, 19, 23, 21,
2234 21, 23, 22, 21, 22, 20,
2235 22, 16, 18, 22, 18, 20,
jvanverthc3d0e422016-08-25 08:12:35 -07002236
Brian Salomon289e3d82016-12-14 15:52:56 -05002237 // corners
2238 0, 1, 5, 0, 5, 4,
2239 2, 3, 7, 2, 7, 6,
2240 8, 9, 13, 8, 13, 12,
2241 10, 11, 15, 10, 15, 14,
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +00002242
Brian Salomon289e3d82016-12-14 15:52:56 -05002243 // edges
2244 1, 2, 6, 1, 6, 5,
2245 4, 5, 9, 4, 9, 8,
2246 6, 7, 11, 6, 11, 10,
2247 9, 10, 14, 9, 14, 13,
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +00002248
Brian Salomon289e3d82016-12-14 15:52:56 -05002249 // center
2250 // we place this at the end so that we can ignore these indices when not rendering as filled
2251 5, 6, 10, 5, 10, 9,
2252 // clang-format on
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002253};
Brian Salomon289e3d82016-12-14 15:52:56 -05002254
jvanverth84839f62016-08-29 10:16:40 -07002255// fill and standard stroke indices skip the overstroke "ring"
Brian Salomon289e3d82016-12-14 15:52:56 -05002256static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002257
jvanverth84839f62016-08-29 10:16:40 -07002258// overstroke count is arraysize minus the center indices
2259static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6;
2260// fill count skips overstroke indices and includes center
Brian Salomon289e3d82016-12-14 15:52:56 -05002261static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
jvanverth84839f62016-08-29 10:16:40 -07002262// stroke count is fill count minus center indices
jvanverthc3d0e422016-08-25 08:12:35 -07002263static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6;
2264static const int kVertsPerStandardRRect = 16;
jvanverth6a397612016-08-26 08:15:33 -07002265static const int kVertsPerOverstrokeRRect = 24;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002266
jvanverthc3d0e422016-08-25 08:12:35 -07002267enum RRectType {
2268 kFill_RRectType,
2269 kStroke_RRectType,
jvanverth84839f62016-08-29 10:16:40 -07002270 kOverstroke_RRectType,
jvanverthc3d0e422016-08-25 08:12:35 -07002271};
2272
jvanverth84839f62016-08-29 10:16:40 -07002273static int rrect_type_to_vert_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002274 switch (type) {
2275 case kFill_RRectType:
2276 case kStroke_RRectType:
2277 return kVertsPerStandardRRect;
2278 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002279 return kVertsPerOverstrokeRRect;
2280 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002281 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002282 return 0;
jvanverth84839f62016-08-29 10:16:40 -07002283}
2284
2285static int rrect_type_to_index_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002286 switch (type) {
2287 case kFill_RRectType:
2288 return kIndicesPerFillRRect;
2289 case kStroke_RRectType:
2290 return kIndicesPerStrokeRRect;
2291 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002292 return kIndicesPerOverstrokeRRect;
2293 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002294 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002295 return 0;
jvanverth84839f62016-08-29 10:16:40 -07002296}
2297
2298static const uint16_t* rrect_type_to_indices(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002299 switch (type) {
2300 case kFill_RRectType:
2301 case kStroke_RRectType:
2302 return gStandardRRectIndices;
2303 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002304 return gOverstrokeRRectIndices;
2305 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002306 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002307 return 0;
bsalomoned0bcad2015-05-04 10:36:42 -07002308}
2309
joshualitt76e7fb62015-02-11 08:52:27 -08002310///////////////////////////////////////////////////////////////////////////////////////////////////
2311
Robert Phillips79839d42016-10-06 15:03:34 -04002312// For distance computations in the interior of filled rrects we:
2313//
2314// add a interior degenerate (point or line) rect
2315// each vertex of that rect gets -outerRad as its radius
2316// this makes the computation of the distance to the outer edge be negative
2317// negative values are caught and then handled differently in the GP's onEmitCode
2318// each vertex is also given the normalized x & y distance from the interior rect's edge
2319// the GP takes the min of those depths +1 to get the normalized distance to the outer edge
2320
Brian Salomon05441c42017-05-15 16:45:49 -04002321class CircularRRectOp : public GrMeshDrawOp {
2322private:
2323 using Helper = GrSimpleMeshDrawOpHelper;
2324
joshualitt76e7fb62015-02-11 08:52:27 -08002325public:
Brian Salomon25a88092016-12-01 09:36:50 -05002326 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002327
bsalomon4b4a7cc2016-07-08 04:42:54 -07002328 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
2329 // whether the rrect is only stroked or stroked and filled.
Robert Phillipsb97da532019-02-12 15:24:12 -05002330 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002331 GrPaint&& paint,
2332 const SkMatrix& viewMatrix,
2333 const SkRect& devRect,
2334 float devRadius,
2335 float devStrokeWidth,
2336 bool strokeOnly) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002337 return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04002338 devRect, devRadius,
2339 devStrokeWidth, strokeOnly);
Brian Salomon05441c42017-05-15 16:45:49 -04002340 }
Greg Daniel2655ede2019-04-10 00:49:28 +00002341 CircularRRectOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002342 const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius,
2343 float devStrokeWidth, bool strokeOnly)
Brian Salomon05441c42017-05-15 16:45:49 -04002344 : INHERITED(ClassID())
2345 , fViewMatrixIfUsingLocalCoords(viewMatrix)
Greg Daniel2655ede2019-04-10 00:49:28 +00002346 , fHelper(helperArgs, GrAAType::kCoverage) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002347 SkRect bounds = devRect;
2348 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
2349 SkScalar innerRadius = 0.0f;
2350 SkScalar outerRadius = devRadius;
2351 SkScalar halfWidth = 0;
jvanverth84839f62016-08-29 10:16:40 -07002352 RRectType type = kFill_RRectType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002353 if (devStrokeWidth > 0) {
2354 if (SkScalarNearlyZero(devStrokeWidth)) {
2355 halfWidth = SK_ScalarHalf;
2356 } else {
2357 halfWidth = SkScalarHalf(devStrokeWidth);
2358 }
joshualitt76e7fb62015-02-11 08:52:27 -08002359
bsalomon4b4a7cc2016-07-08 04:42:54 -07002360 if (strokeOnly) {
jvanverthc3d0e422016-08-25 08:12:35 -07002361 // Outset stroke by 1/4 pixel
2362 devStrokeWidth += 0.25f;
2363 // If stroke is greater than width or height, this is still a fill
2364 // Otherwise we compute stroke params
Brian Salomon289e3d82016-12-14 15:52:56 -05002365 if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) {
jvanverthc3d0e422016-08-25 08:12:35 -07002366 innerRadius = devRadius - halfWidth;
jvanverth84839f62016-08-29 10:16:40 -07002367 type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
jvanverthc3d0e422016-08-25 08:12:35 -07002368 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002369 }
2370 outerRadius += halfWidth;
2371 bounds.outset(halfWidth, halfWidth);
2372 }
bsalomoncdaa97b2016-03-08 08:30:14 -08002373
Greg Daniel2655ede2019-04-10 00:49:28 +00002374 // The radii are outset for two reasons. First, it allows the shader to simply perform
2375 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
2376 // Second, the outer radius is used to compute the verts of the bounding box that is
2377 // rendered and the outset ensures the box will cover all partially covered by the rrect
2378 // corners.
2379 outerRadius += SK_ScalarHalf;
2380 innerRadius -= SK_ScalarHalf;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002381
Greg Daniel2655ede2019-04-10 00:49:28 +00002382 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07002383
Greg Daniel2655ede2019-04-10 00:49:28 +00002384 // Expand the rect for aa to generate correct vertices.
2385 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002386
Brian Salomon05441c42017-05-15 16:45:49 -04002387 fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type});
jvanverth84839f62016-08-29 10:16:40 -07002388 fVertCount = rrect_type_to_vert_count(type);
2389 fIndexCount = rrect_type_to_index_count(type);
2390 fAllFill = (kFill_RRectType == type);
joshualitt76e7fb62015-02-11 08:52:27 -08002391 }
2392
Brian Salomon289e3d82016-12-14 15:52:56 -05002393 const char* name() const override { return "CircularRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002394
Chris Dalton1706cbf2019-05-21 19:35:29 -06002395 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002396 fHelper.visitProxies(func);
2397 }
2398
Brian Osman9a390ac2018-11-12 09:47:48 -05002399#ifdef SK_DEBUG
jvanverthc3d0e422016-08-25 08:12:35 -07002400 SkString dumpInfo() const override {
2401 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002402 for (int i = 0; i < fRRects.count(); ++i) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002403 string.appendf(
2404 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
2405 "InnerRad: %.2f, OuterRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002406 fRRects[i].fColor.toBytes_RGBA(), fRRects[i].fDevBounds.fLeft,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002407 fRRects[i].fDevBounds.fTop, fRRects[i].fDevBounds.fRight,
2408 fRRects[i].fDevBounds.fBottom, fRRects[i].fInnerRadius,
2409 fRRects[i].fOuterRadius);
jvanverthc3d0e422016-08-25 08:12:35 -07002410 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002411 string += fHelper.dumpInfo();
2412 string += INHERITED::dumpInfo();
jvanverthc3d0e422016-08-25 08:12:35 -07002413 return string;
2414 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002415#endif
jvanverthc3d0e422016-08-25 08:12:35 -07002416
Brian Osman5ced0bf2019-03-15 10:15:29 -04002417 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
2418 GrFSAAType fsaaType, GrClampType clampType) override {
Brian Osmancf860852018-10-31 14:04:39 -04002419 SkPMColor4f* color = &fRRects.front().fColor;
Brian Osman5ced0bf2019-03-15 10:15:29 -04002420 return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002421 GrProcessorAnalysisCoverage::kSingleChannel, color,
2422 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002423 }
2424
2425 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2426
Brian Salomon92aee3d2016-12-21 09:20:25 -05002427private:
Brian Osmana1d4eb92018-12-06 16:33:10 -05002428 static void FillInOverstrokeVerts(GrVertexWriter& verts, const SkRect& bounds, SkScalar smInset,
Brian Salomon289e3d82016-12-14 15:52:56 -05002429 SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002430 SkScalar innerRadius, const GrVertexColor& color) {
Robert Phillips79839d42016-10-06 15:03:34 -04002431 SkASSERT(smInset < bigInset);
2432
2433 // TL
Brian Osmana1d4eb92018-12-06 16:33:10 -05002434 verts.write(bounds.fLeft + smInset, bounds.fTop + smInset,
2435 color,
2436 xOffset, 0.0f,
2437 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002438
2439 // TR
Brian Osmana1d4eb92018-12-06 16:33:10 -05002440 verts.write(bounds.fRight - smInset, bounds.fTop + smInset,
2441 color,
2442 xOffset, 0.0f,
2443 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002444
Brian Osmana1d4eb92018-12-06 16:33:10 -05002445 verts.write(bounds.fLeft + bigInset, bounds.fTop + bigInset,
2446 color,
2447 0.0f, 0.0f,
2448 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002449
Brian Osmana1d4eb92018-12-06 16:33:10 -05002450 verts.write(bounds.fRight - bigInset, bounds.fTop + bigInset,
2451 color,
2452 0.0f, 0.0f,
2453 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002454
Brian Osmana1d4eb92018-12-06 16:33:10 -05002455 verts.write(bounds.fLeft + bigInset, bounds.fBottom - bigInset,
2456 color,
2457 0.0f, 0.0f,
2458 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002459
Brian Osmana1d4eb92018-12-06 16:33:10 -05002460 verts.write(bounds.fRight - bigInset, bounds.fBottom - bigInset,
2461 color,
2462 0.0f, 0.0f,
2463 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002464
2465 // BL
Brian Osmana1d4eb92018-12-06 16:33:10 -05002466 verts.write(bounds.fLeft + smInset, bounds.fBottom - smInset,
2467 color,
2468 xOffset, 0.0f,
2469 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002470
2471 // BR
Brian Osmana1d4eb92018-12-06 16:33:10 -05002472 verts.write(bounds.fRight - smInset, bounds.fBottom - smInset,
2473 color,
2474 xOffset, 0.0f,
2475 outerRadius, innerRadius);
Robert Phillips79839d42016-10-06 15:03:34 -04002476 }
2477
Brian Salomon91326c32017-08-09 16:02:19 -04002478 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002479 // Invert the view matrix as a local matrix (if any other processors require coords).
2480 SkMatrix localMatrix;
2481 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002482 return;
2483 }
2484
2485 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002486 sk_sp<GrGeometryProcessor> gp(
Greg Daniel2655ede2019-04-10 00:49:28 +00002487 new CircleGeometryProcessor(!fAllFill, false, false, false, false, fWideColor,
2488 localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002489
Brian Salomon12d22642019-01-29 14:38:50 -05002490 sk_sp<const GrBuffer> vertexBuffer;
jvanverth84839f62016-08-29 10:16:40 -07002491 int firstVertex;
joshualitt76e7fb62015-02-11 08:52:27 -08002492
Brian Osmana1d4eb92018-12-06 16:33:10 -05002493 GrVertexWriter verts{target->makeVertexSpace(gp->vertexStride(), fVertCount,
2494 &vertexBuffer, &firstVertex)};
2495 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002496 SkDebugf("Could not allocate vertices\n");
2497 return;
2498 }
2499
Brian Salomon12d22642019-01-29 14:38:50 -05002500 sk_sp<const GrBuffer> indexBuffer;
jvanverth84839f62016-08-29 10:16:40 -07002501 int firstIndex = 0;
2502 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
2503 if (!indices) {
2504 SkDebugf("Could not allocate indices\n");
2505 return;
2506 }
2507
2508 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04002509 for (const auto& rrect : fRRects) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002510 GrVertexColor color(rrect.fColor, fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002511 SkScalar outerRadius = rrect.fOuterRadius;
2512 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002513
Brian Salomon289e3d82016-12-14 15:52:56 -05002514 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius,
2515 bounds.fBottom - outerRadius, bounds.fBottom};
joshualitt76e7fb62015-02-11 08:52:27 -08002516
Brian Salomon289e3d82016-12-14 15:52:56 -05002517 SkScalar yOuterRadii[4] = {-1, 0, 0, 1};
joshualitt76e7fb62015-02-11 08:52:27 -08002518 // The inner radius in the vertex data must be specified in normalized space.
jvanverth84839f62016-08-29 10:16:40 -07002519 // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
Brian Salomone23bffd2017-06-02 11:01:10 -04002520 SkScalar innerRadius = rrect.fType != kFill_RRectType
2521 ? rrect.fInnerRadius / rrect.fOuterRadius
2522 : -1.0f / rrect.fOuterRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002523 for (int i = 0; i < 4; ++i) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002524 verts.write(bounds.fLeft, yCoords[i],
2525 color,
2526 -1.0f, yOuterRadii[i],
2527 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002528
Brian Osmana1d4eb92018-12-06 16:33:10 -05002529 verts.write(bounds.fLeft + outerRadius, yCoords[i],
2530 color,
2531 0.0f, yOuterRadii[i],
2532 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002533
Brian Osmana1d4eb92018-12-06 16:33:10 -05002534 verts.write(bounds.fRight - outerRadius, yCoords[i],
2535 color,
2536 0.0f, yOuterRadii[i],
2537 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002538
Brian Osmana1d4eb92018-12-06 16:33:10 -05002539 verts.write(bounds.fRight, yCoords[i],
2540 color,
2541 1.0f, yOuterRadii[i],
2542 outerRadius, innerRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002543 }
jvanverthc3d0e422016-08-25 08:12:35 -07002544 // Add the additional vertices for overstroked rrects.
jvanvertha4f1af82016-08-29 07:17:47 -07002545 // Effectively this is an additional stroked rrect, with its
2546 // outer radius = outerRadius - innerRadius, and inner radius = 0.
2547 // This will give us correct AA in the center and the correct
2548 // distance to the outer edge.
jvanverthc3d0e422016-08-25 08:12:35 -07002549 //
jvanvertha4f1af82016-08-29 07:17:47 -07002550 // Also, the outer offset is a constant vector pointing to the right, which
2551 // guarantees that the distance value along the outer rectangle is constant.
Brian Salomon05441c42017-05-15 16:45:49 -04002552 if (kOverstroke_RRectType == rrect.fType) {
2553 SkASSERT(rrect.fInnerRadius <= 0.0f);
Robert Phillips79839d42016-10-06 15:03:34 -04002554
Brian Salomon05441c42017-05-15 16:45:49 -04002555 SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002556 // this is the normalized distance from the outer rectangle of this
2557 // geometry to the outer edge
Brian Salomon05441c42017-05-15 16:45:49 -04002558 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002559
Brian Osmana1d4eb92018-12-06 16:33:10 -05002560 FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002561 overstrokeOuterRadius, 0.0f, color);
Robert Phillips79839d42016-10-06 15:03:34 -04002562 }
jvanverth6a397612016-08-26 08:15:33 -07002563
Brian Salomon05441c42017-05-15 16:45:49 -04002564 const uint16_t* primIndices = rrect_type_to_indices(rrect.fType);
2565 const int primIndexCount = rrect_type_to_index_count(rrect.fType);
jvanverth84839f62016-08-29 10:16:40 -07002566 for (int i = 0; i < primIndexCount; ++i) {
2567 *indices++ = primIndices[i] + currStartVertex;
2568 }
2569
Brian Salomon05441c42017-05-15 16:45:49 -04002570 currStartVertex += rrect_type_to_vert_count(rrect.fType);
joshualitt76e7fb62015-02-11 08:52:27 -08002571 }
2572
Brian Salomon7eae3e02018-08-07 14:02:38 +00002573 GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -05002574 mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
Brian Salomon7eae3e02018-08-07 14:02:38 +00002575 GrPrimitiveRestart::kNo);
Brian Salomon12d22642019-01-29 14:38:50 -05002576 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002577 target->recordDraw(std::move(gp), mesh);
2578 }
2579
2580 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2581 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002582 }
2583
Brian Salomon7eae3e02018-08-07 14:02:38 +00002584 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002585 CircularRRectOp* that = t->cast<CircularRRectOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05002586
2587 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05002588 if (fVertCount + that->fVertCount > 65536) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002589 return CombineResult::kCannotCombine;
Jim Van Verth8cefe402017-02-09 11:36:37 -05002590 }
2591
Brian Salomon05441c42017-05-15 16:45:49 -04002592 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002593 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002594 }
2595
Brian Salomon05441c42017-05-15 16:45:49 -04002596 if (fHelper.usesLocalCoords() &&
2597 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002598 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002599 }
2600
Brian Salomon05441c42017-05-15 16:45:49 -04002601 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
jvanverth84839f62016-08-29 10:16:40 -07002602 fVertCount += that->fVertCount;
2603 fIndexCount += that->fIndexCount;
2604 fAllFill = fAllFill && that->fAllFill;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002605 fWideColor = fWideColor || that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002606 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002607 }
2608
Brian Salomon05441c42017-05-15 16:45:49 -04002609 struct RRect {
Brian Osmancf860852018-10-31 14:04:39 -04002610 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002611 SkScalar fInnerRadius;
2612 SkScalar fOuterRadius;
2613 SkRect fDevBounds;
jvanverth84839f62016-08-29 10:16:40 -07002614 RRectType fType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002615 };
2616
Brian Salomon289e3d82016-12-14 15:52:56 -05002617 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002618 Helper fHelper;
Brian Salomon289e3d82016-12-14 15:52:56 -05002619 int fVertCount;
2620 int fIndexCount;
2621 bool fAllFill;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002622 bool fWideColor;
Brian Salomon05441c42017-05-15 16:45:49 -04002623 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002624
Brian Salomon05441c42017-05-15 16:45:49 -04002625 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002626};
2627
jvanverth84839f62016-08-29 10:16:40 -07002628static const int kNumRRectsInIndexBuffer = 256;
2629
2630GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2631GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
Brian Salomond28a79d2017-10-16 13:01:07 -04002632static sk_sp<const GrBuffer> get_rrect_index_buffer(RRectType type,
2633 GrResourceProvider* resourceProvider) {
jvanverth84839f62016-08-29 10:16:40 -07002634 GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2635 GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
2636 switch (type) {
2637 case kFill_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002638 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002639 gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer,
2640 kVertsPerStandardRRect, gRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002641 case kStroke_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002642 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002643 gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer,
2644 kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002645 default:
2646 SkASSERT(false);
2647 return nullptr;
Brian Salomon23356442018-11-30 15:33:19 -05002648 }
jvanverth84839f62016-08-29 10:16:40 -07002649}
2650
Brian Salomon05441c42017-05-15 16:45:49 -04002651class EllipticalRRectOp : public GrMeshDrawOp {
2652private:
2653 using Helper = GrSimpleMeshDrawOpHelper;
2654
joshualitt76e7fb62015-02-11 08:52:27 -08002655public:
Brian Salomon25a88092016-12-01 09:36:50 -05002656 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002657
bsalomon4b4a7cc2016-07-08 04:42:54 -07002658 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
2659 // whether the rrect is only stroked or stroked and filled.
Robert Phillipsb97da532019-02-12 15:24:12 -05002660 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002661 GrPaint&& paint,
2662 const SkMatrix& viewMatrix,
2663 const SkRect& devRect,
2664 float devXRadius,
2665 float devYRadius,
2666 SkVector devStrokeWidths,
2667 bool strokeOnly) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002668 SkASSERT(devXRadius > 0.5);
2669 SkASSERT(devYRadius > 0.5);
2670 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0));
2671 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0));
bsalomon4b4a7cc2016-07-08 04:42:54 -07002672 if (devStrokeWidths.fX > 0) {
2673 if (SkScalarNearlyZero(devStrokeWidths.length())) {
2674 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf);
2675 } else {
2676 devStrokeWidths.scale(SK_ScalarHalf);
2677 }
joshualitt76e7fb62015-02-11 08:52:27 -08002678
bsalomon4b4a7cc2016-07-08 04:42:54 -07002679 // we only handle thick strokes for near-circular ellipses
2680 if (devStrokeWidths.length() > SK_ScalarHalf &&
Brian Salomon289e3d82016-12-14 15:52:56 -05002681 (SK_ScalarHalf * devXRadius > devYRadius ||
2682 SK_ScalarHalf * devYRadius > devXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002683 return nullptr;
2684 }
2685
2686 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon289e3d82016-12-14 15:52:56 -05002687 if (devStrokeWidths.fX * (devYRadius * devYRadius) <
2688 (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002689 return nullptr;
2690 }
Brian Salomon289e3d82016-12-14 15:52:56 -05002691 if (devStrokeWidths.fY * (devXRadius * devXRadius) <
2692 (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002693 return nullptr;
2694 }
Brian Salomon05441c42017-05-15 16:45:49 -04002695 }
Robert Phillips7c525e62018-06-12 10:11:12 -04002696 return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint),
Greg Daniel2655ede2019-04-10 00:49:28 +00002697 viewMatrix, devRect,
Brian Salomon05441c42017-05-15 16:45:49 -04002698 devXRadius, devYRadius, devStrokeWidths,
2699 strokeOnly);
2700 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002701
Greg Daniel2655ede2019-04-10 00:49:28 +00002702 EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -04002703 const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius,
2704 float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly)
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002705 : INHERITED(ClassID())
2706 , fHelper(helperArgs, GrAAType::kCoverage)
2707 , fUseScale(false) {
Brian Salomon05441c42017-05-15 16:45:49 -04002708 SkScalar innerXRadius = 0.0f;
2709 SkScalar innerYRadius = 0.0f;
2710 SkRect bounds = devRect;
2711 bool stroked = false;
2712 if (devStrokeHalfWidths.fX > 0) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002713 // this is legit only if scale & translation (which should be the case at the moment)
2714 if (strokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04002715 innerXRadius = devXRadius - devStrokeHalfWidths.fX;
2716 innerYRadius = devYRadius - devStrokeHalfWidths.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002717 stroked = (innerXRadius >= 0 && innerYRadius >= 0);
2718 }
2719
Brian Salomon05441c42017-05-15 16:45:49 -04002720 devXRadius += devStrokeHalfWidths.fX;
2721 devYRadius += devStrokeHalfWidths.fY;
2722 bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002723 }
2724
Brian Salomon05441c42017-05-15 16:45:49 -04002725 fStroked = stroked;
2726 fViewMatrixIfUsingLocalCoords = viewMatrix;
Greg Daniel2655ede2019-04-10 00:49:28 +00002727 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
2728 // Expand the rect for aa in order to generate the correct vertices.
2729 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
Brian Salomon05441c42017-05-15 16:45:49 -04002730 fRRects.emplace_back(
2731 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
joshualitt76e7fb62015-02-11 08:52:27 -08002732 }
2733
Brian Salomon289e3d82016-12-14 15:52:56 -05002734 const char* name() const override { return "EllipticalRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002735
Chris Dalton1706cbf2019-05-21 19:35:29 -06002736 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002737 fHelper.visitProxies(func);
2738 }
2739
Brian Osman9a390ac2018-11-12 09:47:48 -05002740#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -05002741 SkString dumpInfo() const override {
2742 SkString string;
2743 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04002744 for (const auto& geo : fRRects) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002745 string.appendf(
2746 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
2747 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
Brian Osmancf860852018-10-31 14:04:39 -04002748 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop,
Brian Osman1be2b7c2018-10-29 16:07:15 -04002749 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius,
2750 geo.fInnerXRadius, geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002751 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002752 string += fHelper.dumpInfo();
2753 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002754 return string;
2755 }
Brian Osman9a390ac2018-11-12 09:47:48 -05002756#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -05002757
Brian Osman5ced0bf2019-03-15 10:15:29 -04002758 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
2759 GrFSAAType fsaaType, GrClampType clampType) override {
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002760 fUseScale = !caps.shaderCaps()->floatIs32Bits();
Brian Osmancf860852018-10-31 14:04:39 -04002761 SkPMColor4f* color = &fRRects.front().fColor;
Brian Osman5ced0bf2019-03-15 10:15:29 -04002762 return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
Brian Osman8fa7ab42019-03-18 10:22:42 -04002763 GrProcessorAnalysisCoverage::kSingleChannel, color,
2764 &fWideColor);
Brian Salomon05441c42017-05-15 16:45:49 -04002765 }
2766
2767 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2768
bsalomone46f9fe2015-08-18 06:05:14 -07002769private:
Brian Salomon91326c32017-08-09 16:02:19 -04002770 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002771 SkMatrix localMatrix;
2772 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002773 return;
2774 }
2775
2776 // Setup geometry processor
Greg Daniel2655ede2019-04-10 00:49:28 +00002777 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002778 localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002779
bsalomonb5238a72015-05-05 07:49:49 -07002780 // drop out the middle quad if we're stroked
jvanverthc3d0e422016-08-25 08:12:35 -07002781 int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
Brian Salomond28a79d2017-10-16 13:01:07 -04002782 sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer(
2783 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider());
joshualitt76e7fb62015-02-11 08:52:27 -08002784
Brian Salomon12d22642019-01-29 14:38:50 -05002785 if (!indexBuffer) {
2786 SkDebugf("Could not allocate indices\n");
2787 return;
2788 }
Brian Osmana1d4eb92018-12-06 16:33:10 -05002789 PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(),
Brian Salomon12d22642019-01-29 14:38:50 -05002790 std::move(indexBuffer), kVertsPerStandardRRect, indicesPerInstance,
Brian Salomon7eae3e02018-08-07 14:02:38 +00002791 fRRects.count());
Brian Osmana1d4eb92018-12-06 16:33:10 -05002792 GrVertexWriter verts{helper.vertices()};
Brian Salomon12d22642019-01-29 14:38:50 -05002793 if (!verts.fPtr) {
joshualitt4b31de82015-03-05 14:33:41 -08002794 SkDebugf("Could not allocate vertices\n");
2795 return;
2796 }
2797
Brian Salomon05441c42017-05-15 16:45:49 -04002798 for (const auto& rrect : fRRects) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002799 GrVertexColor color(rrect.fColor, fWideColor);
joshualitt76e7fb62015-02-11 08:52:27 -08002800 // Compute the reciprocals of the radii here to save time in the shader
Brian Osmana1d4eb92018-12-06 16:33:10 -05002801 float reciprocalRadii[4] = {
2802 SkScalarInvert(rrect.fXRadius),
2803 SkScalarInvert(rrect.fYRadius),
2804 SkScalarInvert(rrect.fInnerXRadius),
2805 SkScalarInvert(rrect.fInnerYRadius)
2806 };
joshualitt76e7fb62015-02-11 08:52:27 -08002807
2808 // Extend the radii out half a pixel to antialias.
Greg Daniel2655ede2019-04-10 00:49:28 +00002809 SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf;
2810 SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf;
joshualitt76e7fb62015-02-11 08:52:27 -08002811
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002812 SkScalar xMaxOffset = xOuterRadius;
2813 SkScalar yMaxOffset = yOuterRadius;
2814 if (!fStroked) {
2815 // For filled rrects we map a unit circle in the vertex attributes rather than
2816 // computing an ellipse and modifying that distance, so we normalize to 1.
2817 xMaxOffset /= rrect.fXRadius;
2818 yMaxOffset /= rrect.fYRadius;
2819 }
2820
Brian Salomon05441c42017-05-15 16:45:49 -04002821 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002822
Brian Salomon289e3d82016-12-14 15:52:56 -05002823 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius,
2824 bounds.fBottom - yOuterRadius, bounds.fBottom};
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002825 SkScalar yOuterOffsets[4] = {yMaxOffset,
Brian Salomon289e3d82016-12-14 15:52:56 -05002826 SK_ScalarNearlyZero, // we're using inversesqrt() in
2827 // shader, so can't be exactly 0
Jim Van Verthb1b87d92018-06-28 10:46:05 -04002828 SK_ScalarNearlyZero, yMaxOffset};
joshualitt76e7fb62015-02-11 08:52:27 -08002829
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002830 auto maybeScale = GrVertexWriter::If(fUseScale, SkTMax(rrect.fXRadius, rrect.fYRadius));
joshualitt76e7fb62015-02-11 08:52:27 -08002831 for (int i = 0; i < 4; ++i) {
Brian Osmana1d4eb92018-12-06 16:33:10 -05002832 verts.write(bounds.fLeft, yCoords[i],
2833 color,
2834 xMaxOffset, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002835 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002836 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002837
Brian Osmana1d4eb92018-12-06 16:33:10 -05002838 verts.write(bounds.fLeft + xOuterRadius, yCoords[i],
2839 color,
2840 SK_ScalarNearlyZero, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002841 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002842 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002843
Brian Osmana1d4eb92018-12-06 16:33:10 -05002844 verts.write(bounds.fRight - xOuterRadius, yCoords[i],
2845 color,
2846 SK_ScalarNearlyZero, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002847 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002848 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002849
Brian Osmana1d4eb92018-12-06 16:33:10 -05002850 verts.write(bounds.fRight, yCoords[i],
2851 color,
2852 xMaxOffset, yOuterOffsets[i],
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002853 maybeScale,
Brian Osmana1d4eb92018-12-06 16:33:10 -05002854 reciprocalRadii);
joshualitt76e7fb62015-02-11 08:52:27 -08002855 }
2856 }
Chris Dalton07cdcfc92019-02-26 11:13:22 -07002857 helper.recordDraw(target, std::move(gp));
2858 }
2859
2860 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
2861 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
joshualitt76e7fb62015-02-11 08:52:27 -08002862 }
2863
Brian Salomon7eae3e02018-08-07 14:02:38 +00002864 CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002865 EllipticalRRectOp* that = t->cast<EllipticalRRectOp>();
bsalomonabd30f52015-08-13 13:34:48 -07002866
Brian Salomon05441c42017-05-15 16:45:49 -04002867 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002868 return CombineResult::kCannotCombine;
joshualitt8cab9a72015-07-16 09:13:50 -07002869 }
2870
bsalomoncdaa97b2016-03-08 08:30:14 -08002871 if (fStroked != that->fStroked) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002872 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002873 }
2874
Brian Salomon05441c42017-05-15 16:45:49 -04002875 if (fHelper.usesLocalCoords() &&
2876 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
Brian Salomon7eae3e02018-08-07 14:02:38 +00002877 return CombineResult::kCannotCombine;
joshualitt76e7fb62015-02-11 08:52:27 -08002878 }
Brian Salomonea26d6b2018-01-23 20:33:21 +00002879
Brian Salomon05441c42017-05-15 16:45:49 -04002880 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
Brian Osmana1d4eb92018-12-06 16:33:10 -05002881 fWideColor = fWideColor || that->fWideColor;
Brian Salomon7eae3e02018-08-07 14:02:38 +00002882 return CombineResult::kMerged;
joshualitt76e7fb62015-02-11 08:52:27 -08002883 }
2884
Brian Salomon05441c42017-05-15 16:45:49 -04002885 struct RRect {
Brian Osmancf860852018-10-31 14:04:39 -04002886 SkPMColor4f fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002887 SkScalar fXRadius;
2888 SkScalar fYRadius;
2889 SkScalar fInnerXRadius;
2890 SkScalar fInnerYRadius;
2891 SkRect fDevBounds;
2892 };
2893
Brian Salomon289e3d82016-12-14 15:52:56 -05002894 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002895 Helper fHelper;
2896 bool fStroked;
Brian Osmana1d4eb92018-12-06 16:33:10 -05002897 bool fWideColor;
Jim Van Verth20ae25c2019-03-29 08:50:41 -04002898 bool fUseScale;
Brian Salomon05441c42017-05-15 16:45:49 -04002899 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002900
Brian Salomon05441c42017-05-15 16:45:49 -04002901 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002902};
2903
Robert Phillipsb97da532019-02-12 15:24:12 -05002904static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002905 GrPaint&& paint,
Brian Salomon05441c42017-05-15 16:45:49 -04002906 const SkMatrix& viewMatrix,
2907 const SkRRect& rrect,
2908 const SkStrokeRec& stroke) {
joshualitt3e708c52015-04-30 13:49:27 -07002909 SkASSERT(viewMatrix.rectStaysRect());
2910 SkASSERT(rrect.isSimple());
2911 SkASSERT(!rrect.isOval());
commit-bot@chromium.orge0a868c2013-11-22 07:02:11 +00002912
Brian Salomon53e4c3c2016-12-21 11:38:53 -05002913 // RRect ops only handle simple, but not too simple, rrects.
2914 // Do any matrix crunching before we reset the draw state for device coords.
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002915 const SkRect& rrectBounds = rrect.getBounds();
joshualittd96a67b2015-05-05 14:09:05 -07002916 SkRect bounds;
2917 viewMatrix.mapRect(&bounds, rrectBounds);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002918
Mike Reed242135a2018-02-22 13:41:39 -05002919 SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
Brian Salomon289e3d82016-12-14 15:52:56 -05002920 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
2921 viewMatrix[SkMatrix::kMSkewY] * radii.fY);
2922 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
2923 viewMatrix[SkMatrix::kMScaleY] * radii.fY);
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +00002924
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002925 SkStrokeRec::Style style = stroke.getStyle();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002926
bsalomon4b4a7cc2016-07-08 04:42:54 -07002927 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
2928 SkVector scaledStroke = {-1, -1};
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002929 SkScalar strokeWidth = stroke.getWidth();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002930
Brian Salomon289e3d82016-12-14 15:52:56 -05002931 bool isStrokeOnly =
2932 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002933 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
2934
jvanverthc3d0e422016-08-25 08:12:35 -07002935 bool isCircular = (xRadius == yRadius);
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002936 if (hasStroke) {
2937 if (SkStrokeRec::kHairline_Style == style) {
2938 scaledStroke.set(1, 1);
2939 } else {
Brian Salomon289e3d82016-12-14 15:52:56 -05002940 scaledStroke.fX = SkScalarAbs(
2941 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
2942 scaledStroke.fY = SkScalarAbs(
2943 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002944 }
2945
jvanverthc3d0e422016-08-25 08:12:35 -07002946 isCircular = isCircular && scaledStroke.fX == scaledStroke.fY;
2947 // for non-circular rrects, if half of strokewidth is greater than radius,
2948 // we don't handle that right now
Brian Salomon289e3d82016-12-14 15:52:56 -05002949 if (!isCircular && (SK_ScalarHalf * scaledStroke.fX > xRadius ||
2950 SK_ScalarHalf * scaledStroke.fY > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07002951 return nullptr;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002952 }
2953 }
2954
Brian Salomon8a97f562019-04-18 14:07:27 -04002955 // The matrix may have a rotation by an odd multiple of 90 degrees.
2956 if (!isCircular && viewMatrix.getScaleX() == 0) {
2957 std::swap(xRadius, yRadius);
2958 std::swap(scaledStroke.fX, scaledStroke.fY);
2959 }
2960
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00002961 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
2962 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
2963 // patch will have fractional coverage. This only matters when the interior is actually filled.
2964 // We could consider falling back to rect rendering here, since a tiny radius is
2965 // indistinguishable from a square corner.
2966 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07002967 return nullptr;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002968 }
2969
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002970 // if the corners are circles, use the circle renderer
jvanverthc3d0e422016-08-25 08:12:35 -07002971 if (isCircular) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002972 return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, xRadius,
Robert Phillips7c525e62018-06-12 10:11:12 -04002973 scaledStroke.fX, isStrokeOnly);
Brian Salomon289e3d82016-12-14 15:52:56 -05002974 // otherwise we use the ellipse renderer
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002975 } else {
Greg Daniel2655ede2019-04-10 00:49:28 +00002976 return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
Robert Phillips7c525e62018-06-12 10:11:12 -04002977 xRadius, yRadius, scaledStroke, isStrokeOnly);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002978 }
joshualitt3e708c52015-04-30 13:49:27 -07002979}
2980
Robert Phillipsb97da532019-02-12 15:24:12 -05002981std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04002982 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00002983 const SkMatrix& viewMatrix,
2984 const SkRRect& rrect,
2985 const SkStrokeRec& stroke,
2986 const GrShaderCaps* shaderCaps) {
robertphillips0cc2f852016-02-24 13:36:56 -08002987 if (rrect.isOval()) {
Greg Daniel2655ede2019-04-10 00:49:28 +00002988 return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(),
Robert Phillips7c525e62018-06-12 10:11:12 -04002989 GrStyle(stroke, nullptr), shaderCaps);
joshualitt3e708c52015-04-30 13:49:27 -07002990 }
2991
2992 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
robertphillips0cc2f852016-02-24 13:36:56 -08002993 return nullptr;
joshualitt3e708c52015-04-30 13:49:27 -07002994 }
2995
Greg Daniel2655ede2019-04-10 00:49:28 +00002996 return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002997}
joshualitt3e708c52015-04-30 13:49:27 -07002998
bsalomon4b4a7cc2016-07-08 04:42:54 -07002999///////////////////////////////////////////////////////////////////////////////
3000
Robert Phillipsb97da532019-02-12 15:24:12 -05003001std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003002 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003003 const SkMatrix& viewMatrix,
3004 const SkRect& oval,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003005 const GrStyle& style,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003006 const GrShaderCaps* shaderCaps) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07003007 // we can draw circles
bsalomon4f3a0ca2016-08-22 13:14:26 -07003008 SkScalar width = oval.width();
Jim Van Verthd952a992017-04-20 17:25:26 -04003009 if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3010 circle_stays_circle(viewMatrix)) {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003011 auto r = width / 2.f;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003012 SkPoint center = {oval.centerX(), oval.centerY()};
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003013 if (style.hasNonDashPathEffect()) {
3014 return nullptr;
3015 } else if (style.isDashed()) {
3016 if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
3017 style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
3018 return nullptr;
3019 }
3020 auto onInterval = style.dashIntervals()[0];
3021 auto offInterval = style.dashIntervals()[1];
3022 if (offInterval == 0) {
3023 GrStyle strokeStyle(style.strokeRec(), nullptr);
Greg Daniel2655ede2019-04-10 00:49:28 +00003024 return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
Robert Phillips7c525e62018-06-12 10:11:12 -04003025 strokeStyle, shaderCaps);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003026 } else if (onInterval == 0) {
3027 // There is nothing to draw but we have no way to indicate that here.
3028 return nullptr;
3029 }
3030 auto angularOnInterval = onInterval / r;
3031 auto angularOffInterval = offInterval / r;
3032 auto phaseAngle = style.dashPhase() / r;
3033 // Currently this function doesn't accept ovals with different start angles, though
3034 // it could.
3035 static const SkScalar kStartAngle = 0.f;
Robert Phillips7c525e62018-06-12 10:11:12 -04003036 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003037 style.strokeRec().getWidth(), kStartAngle,
3038 angularOnInterval, angularOffInterval, phaseAngle);
3039 }
Greg Daniel2655ede2019-04-10 00:49:28 +00003040 return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003041 }
3042
3043 if (style.pathEffect()) {
3044 return nullptr;
bsalomon4b4a7cc2016-07-08 04:42:54 -07003045 }
3046
Stan Ilieveb868aa2017-02-21 11:06:16 -05003047 // prefer the device space ellipse op for batchability
bsalomon4b4a7cc2016-07-08 04:42:54 -07003048 if (viewMatrix.rectStaysRect()) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003049 return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
bsalomon4b4a7cc2016-07-08 04:42:54 -07003050 }
3051
Stan Ilieveb868aa2017-02-21 11:06:16 -05003052 // Otherwise, if we have shader derivative support, render as device-independent
3053 if (shaderCaps->shaderDerivativeSupport()) {
Jim Van Vertha925bb02018-09-25 10:49:52 -04003054 SkScalar a = viewMatrix[SkMatrix::kMScaleX];
3055 SkScalar b = viewMatrix[SkMatrix::kMSkewX];
3056 SkScalar c = viewMatrix[SkMatrix::kMSkewY];
3057 SkScalar d = viewMatrix[SkMatrix::kMScaleY];
3058 // Check for near-degenerate matrix
3059 if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) {
Greg Daniel2655ede2019-04-10 00:49:28 +00003060 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval,
Jim Van Vertha925bb02018-09-25 10:49:52 -04003061 style.strokeRec());
3062 }
Stan Ilieveb868aa2017-02-21 11:06:16 -05003063 }
3064
bsalomon4b4a7cc2016-07-08 04:42:54 -07003065 return nullptr;
3066}
3067
3068///////////////////////////////////////////////////////////////////////////////
joshualitt3e708c52015-04-30 13:49:27 -07003069
Robert Phillipsb97da532019-02-12 15:24:12 -05003070std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -04003071 GrPaint&& paint,
3072 const SkMatrix& viewMatrix,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003073 const SkRect& oval, SkScalar startAngle,
3074 SkScalar sweepAngle, bool useCenter,
3075 const GrStyle& style,
3076 const GrShaderCaps* shaderCaps) {
bsalomon21af9ca2016-08-25 12:29:23 -07003077 SkASSERT(!oval.isEmpty());
3078 SkASSERT(sweepAngle);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003079 SkScalar width = oval.width();
bsalomon21af9ca2016-08-25 12:29:23 -07003080 if (SkScalarAbs(sweepAngle) >= 360.f) {
3081 return nullptr;
3082 }
bsalomon4f3a0ca2016-08-22 13:14:26 -07003083 if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) {
3084 return nullptr;
3085 }
3086 SkPoint center = {oval.centerX(), oval.centerY()};
Brian Salomon289e3d82016-12-14 15:52:56 -05003087 CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle),
3088 useCenter};
Greg Daniel2655ede2019-04-10 00:49:28 +00003089 return CircleOp::Make(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04003090 center, width / 2.f, style, &arcParams);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003091}
3092
3093///////////////////////////////////////////////////////////////////////////////
3094
Hal Canary6f6961e2017-01-31 13:50:44 -05003095#if GR_TEST_UTILS
joshualitt3e708c52015-04-30 13:49:27 -07003096
Brian Salomon05441c42017-05-15 16:45:49 -04003097GR_DRAW_OP_TEST_DEFINE(CircleOp) {
bsalomon4f3a0ca2016-08-22 13:14:26 -07003098 do {
bsalomoncadf75a2016-08-22 14:24:24 -07003099 SkScalar rotate = random->nextSScalar1() * 360.f;
3100 SkScalar translateX = random->nextSScalar1() * 1000.f;
3101 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003102 SkScalar scale;
3103 do {
3104 scale = random->nextSScalar1() * 100.f;
3105 } while (scale == 0);
bsalomoncadf75a2016-08-22 14:24:24 -07003106 SkMatrix viewMatrix;
3107 viewMatrix.setRotate(rotate);
3108 viewMatrix.postTranslate(translateX, translateY);
3109 viewMatrix.postScale(scale, scale);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003110 SkRect circle = GrTest::TestSquare(random);
3111 SkPoint center = {circle.centerX(), circle.centerY()};
3112 SkScalar radius = circle.width() / 2.f;
3113 SkStrokeRec stroke = GrTest::TestStrokeRec(random);
Brian Salomon289e3d82016-12-14 15:52:56 -05003114 CircleOp::ArcParams arcParamsTmp;
3115 const CircleOp::ArcParams* arcParams = nullptr;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003116 if (random->nextBool()) {
3117 arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2;
robertphillips08197b22016-08-23 06:19:15 -07003118 arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f;
3119 arcParamsTmp.fUseCenter = random->nextBool();
bsalomon4f3a0ca2016-08-22 13:14:26 -07003120 arcParams = &arcParamsTmp;
3121 }
Greg Daniel2655ede2019-04-10 00:49:28 +00003122 std::unique_ptr<GrDrawOp> op = CircleOp::Make(context, std::move(paint), viewMatrix,
Robert Phillips7c525e62018-06-12 10:11:12 -04003123 center, radius,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003124 GrStyle(stroke, nullptr), arcParams);
Brian Salomon289e3d82016-12-14 15:52:56 -05003125 if (op) {
Brian Salomon5ec9def2016-12-20 15:34:05 -05003126 return op;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003127 }
Mike Klein16885072018-12-11 09:54:31 -05003128 assert_alive(paint);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003129 } while (true);
joshualitt3e708c52015-04-30 13:49:27 -07003130}
3131
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003132GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) {
3133 SkScalar rotate = random->nextSScalar1() * 360.f;
3134 SkScalar translateX = random->nextSScalar1() * 1000.f;
3135 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003136 SkScalar scale;
3137 do {
3138 scale = random->nextSScalar1() * 100.f;
3139 } while (scale == 0);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003140 SkMatrix viewMatrix;
3141 viewMatrix.setRotate(rotate);
3142 viewMatrix.postTranslate(translateX, translateY);
3143 viewMatrix.postScale(scale, scale);
3144 SkRect circle = GrTest::TestSquare(random);
3145 SkPoint center = {circle.centerX(), circle.centerY()};
3146 SkScalar radius = circle.width() / 2.f;
3147 SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius);
3148 SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f);
3149 SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f);
3150 SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f);
3151 SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f);
Robert Phillips7c525e62018-06-12 10:11:12 -04003152 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix,
3153 center, radius, strokeWidth,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003154 startAngle, onAngle, offAngle, phase);
3155}
3156
Brian Salomon05441c42017-05-15 16:45:49 -04003157GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003158 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt6c891102015-05-13 08:51:49 -07003159 SkRect ellipse = GrTest::TestSquare(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003160 return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
Robert Phillips7c525e62018-06-12 10:11:12 -04003161 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003162}
3163
Brian Salomon05441c42017-05-15 16:45:49 -04003164GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003165 SkMatrix viewMatrix = GrTest::TestMatrix(random);
joshualitt6c891102015-05-13 08:51:49 -07003166 SkRect ellipse = GrTest::TestSquare(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003167 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
Robert Phillips7c525e62018-06-12 10:11:12 -04003168 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003169}
3170
Brian Salomon05441c42017-05-15 16:45:49 -04003171GR_DRAW_OP_TEST_DEFINE(RRectOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003172 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt3e708c52015-04-30 13:49:27 -07003173 const SkRRect& rrect = GrTest::TestRRectSimple(random);
Greg Daniel2655ede2019-04-10 00:49:28 +00003174 return make_rrect_op(context, std::move(paint), viewMatrix, rrect,
Robert Phillips7c525e62018-06-12 10:11:12 -04003175 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003176}
3177
3178#endif