blob: 1655b7632697ad0234cb9d6c87eee8ee1d5f0d76 [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
Brian Salomon289e3d82016-12-14 15:52:56 -05008#include "GrOvalOpFactory.h"
Brian Salomon5ec9def2016-12-20 15:34:05 -05009#include "GrDrawOpTest.h"
joshualitteb2a6762014-12-04 11:35:33 -080010#include "GrGeometryProcessor.h"
Brian Salomon742e31d2016-12-07 17:06:19 -050011#include "GrOpFlushState.h"
joshualitt76e7fb62015-02-11 08:52:27 -080012#include "GrProcessor.h"
bsalomoned0bcad2015-05-04 10:36:42 -070013#include "GrResourceProvider.h"
Brian Salomon94efbf52016-11-29 13:43:05 -050014#include "GrShaderCaps.h"
bsalomon4f3a0ca2016-08-22 13:14:26 -070015#include "GrStyle.h"
Mike Reed242135a2018-02-22 13:41:39 -050016#include "SkRRectPriv.h"
commit-bot@chromium.org81312832013-03-22 18:34:09 +000017#include "SkStrokeRec.h"
egdaniel2d721d32015-11-11 13:06:05 -080018#include "glsl/GrGLSLFragmentShaderBuilder.h"
egdaniele659a582015-11-13 09:55:43 -080019#include "glsl/GrGLSLGeometryProcessor.h"
egdaniel018fb622015-10-28 07:26:40 -070020#include "glsl/GrGLSLProgramDataManager.h"
egdaniel7ea439b2015-12-03 09:20:44 -080021#include "glsl/GrGLSLUniformHandler.h"
egdaniel64c47282015-11-13 06:54:19 -080022#include "glsl/GrGLSLUtil.h"
Brian Salomondad29232016-12-01 16:40:24 -050023#include "glsl/GrGLSLVarying.h"
Chris Daltonc17bf322017-10-24 10:59:03 -060024#include "glsl/GrGLSLVertexGeoBuilder.h"
Brian Salomon89527432016-12-16 09:52:16 -050025#include "ops/GrMeshDrawOp.h"
Brian Salomon05441c42017-05-15 16:45:49 -040026#include "ops/GrSimpleMeshDrawOpHelper.h"
joshualitt76e7fb62015-02-11 08:52:27 -080027
Ben Wagnerf08d1d02018-06-18 15:11:00 -040028#include <utility>
29
commit-bot@chromium.org81312832013-03-22 18:34:09 +000030namespace {
brianosmanbb2ff942016-02-11 14:15:18 -080031
commit-bot@chromium.org81312832013-03-22 18:34:09 +000032struct EllipseVertex {
Brian Salomon289e3d82016-12-14 15:52:56 -050033 SkPoint fPos;
34 GrColor fColor;
35 SkPoint fOffset;
36 SkPoint fOuterRadii;
37 SkPoint fInnerRadii;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +000038};
39
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +000040struct DIEllipseVertex {
Brian Salomon289e3d82016-12-14 15:52:56 -050041 SkPoint fPos;
42 GrColor fColor;
43 SkPoint fOuterOffset;
44 SkPoint fInnerOffset;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +000045};
46
Brian Salomon289e3d82016-12-14 15:52:56 -050047static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); }
commit-bot@chromium.org81312832013-03-22 18:34:09 +000048}
49
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000050///////////////////////////////////////////////////////////////////////////////
51
52/**
bsalomonce1c8862014-12-15 07:11:22 -080053 * The output of this effect is a modulation of the input color and coverage for a circle. It
54 * operates in a space normalized by the circle radius (outer radius in the case of a stroke)
bsalomoncdaa97b2016-03-08 08:30:14 -080055 * with origin at the circle center. Three vertex attributes are used:
bsalomonce1c8862014-12-15 07:11:22 -080056 * vec2f : position in device space of the bounding geometry vertices
bsalomoncdaa97b2016-03-08 08:30:14 -080057 * vec4ub: color
bsalomonce1c8862014-12-15 07:11:22 -080058 * vec4f : (p.xy, outerRad, innerRad)
59 * p is the position in the normalized space.
60 * outerRad is the outerRadius in device space.
61 * innerRad is the innerRadius in normalized space (ignored if not stroking).
bsalomon4f3a0ca2016-08-22 13:14:26 -070062 * Additional clip planes are supported for rendering circular arcs. The additional planes are
63 * either intersected or unioned together. Up to three planes are supported (an initial plane,
64 * a plane intersected with the initial plane, and a plane unioned with the first two). Only two
Brian Salomon53e4c3c2016-12-21 11:38:53 -050065 * are useful for any given arc, but having all three in one instance allows combining different
bsalomon4f3a0ca2016-08-22 13:14:26 -070066 * types of arcs.
Brian Salomon45c92202018-04-10 10:53:58 -040067 * Round caps for stroking are allowed as well. The caps are specified as two circle center points
68 * in the same space as p.xy.
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000069 */
70
bsalomoncdaa97b2016-03-08 08:30:14 -080071class CircleGeometryProcessor : public GrGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000072public:
Brian Salomonea26d6b2018-01-23 20:33:21 +000073 CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane,
Brian Salomon45c92202018-04-10 10:53:58 -040074 bool roundCaps, const SkMatrix& localMatrix)
Ethan Nicholasabff9562017-10-09 10:54:08 -040075 : INHERITED(kCircleGeometryProcessor_ClassID)
Brian Salomon45c92202018-04-10 10:53:58 -040076 , fLocalMatrix(localMatrix)
77 , fStroke(stroke) {
Brian Salomon92be2f72018-06-19 14:33:47 -040078 int cnt = 3;
bsalomon4f3a0ca2016-08-22 13:14:26 -070079 if (clipPlane) {
Brian Salomon92be2f72018-06-19 14:33:47 -040080 fInClipPlane = {"inClipPlane", kHalf3_GrVertexAttribType};
81 ++cnt;
bsalomon4f3a0ca2016-08-22 13:14:26 -070082 }
83 if (isectPlane) {
Brian Salomon92be2f72018-06-19 14:33:47 -040084 fInIsectPlane = {"inIsectPlane", kHalf3_GrVertexAttribType};
85 ++cnt;
bsalomon4f3a0ca2016-08-22 13:14:26 -070086 }
87 if (unionPlane) {
Brian Salomon92be2f72018-06-19 14:33:47 -040088 fInUnionPlane = {"inUnionPlane", kHalf3_GrVertexAttribType};
89 ++cnt;
bsalomon4f3a0ca2016-08-22 13:14:26 -070090 }
Brian Salomon45c92202018-04-10 10:53:58 -040091 if (roundCaps) {
92 SkASSERT(stroke);
93 SkASSERT(clipPlane);
Brian Salomon92be2f72018-06-19 14:33:47 -040094 fInRoundCapCenters = {"inRoundCapCenters", kFloat4_GrVertexAttribType};
95 ++cnt;
Brian Salomon45c92202018-04-10 10:53:58 -040096 }
Brian Salomon92be2f72018-06-19 14:33:47 -040097 this->setVertexAttributeCnt(cnt);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000098 }
99
Brian Salomond3b65972017-03-22 12:05:03 -0400100 ~CircleGeometryProcessor() override {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000101
mtklein36352bf2015-03-25 18:17:31 -0700102 const char* name() const override { return "CircleEdge"; }
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000103
Brian Salomon94efbf52016-11-29 13:43:05 -0500104 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700105 GLSLProcessor::GenKey(*this, caps, b);
106 }
107
Brian Salomon94efbf52016-11-29 13:43:05 -0500108 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700109 return new GLSLProcessor();
110 }
111
112private:
egdaniel57d3b032015-11-13 11:57:27 -0800113 class GLSLProcessor : public GrGLSLGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000114 public:
brianosmanbb2ff942016-02-11 14:15:18 -0800115 GLSLProcessor() {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000116
Brian Salomon289e3d82016-12-14 15:52:56 -0500117 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800118 const CircleGeometryProcessor& cgp = args.fGP.cast<CircleGeometryProcessor>();
egdaniel4ca2e602015-11-18 08:01:26 -0800119 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
egdaniel0eafe792015-11-20 14:01:22 -0800120 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
egdaniel7ea439b2015-12-03 09:20:44 -0800121 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
Chris Dalton60283612018-02-14 13:38:14 -0700122 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
joshualitt2dd1ae02014-12-03 06:24:10 -0800123
joshualittabb52a12015-01-13 15:02:10 -0800124 // emit attributes
bsalomoncdaa97b2016-03-08 08:30:14 -0800125 varyingHandler->emitAttributes(cgp);
Ethan Nicholas8aa45692017-09-20 11:24:15 -0400126 fragBuilder->codeAppend("float4 circleEdge;");
Brian Salomon92be2f72018-06-19 14:33:47 -0400127 varyingHandler->addPassThroughAttribute(cgp.kInCircleEdge, "circleEdge");
128 if (cgp.fInClipPlane.isInitialized()) {
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400129 fragBuilder->codeAppend("half3 clipPlane;");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700130 varyingHandler->addPassThroughAttribute(cgp.fInClipPlane, "clipPlane");
131 }
Brian Salomon92be2f72018-06-19 14:33:47 -0400132 if (cgp.fInIsectPlane.isInitialized()) {
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400133 fragBuilder->codeAppend("half3 isectPlane;");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700134 varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane, "isectPlane");
135 }
Brian Salomon92be2f72018-06-19 14:33:47 -0400136 if (cgp.fInUnionPlane.isInitialized()) {
137 SkASSERT(cgp.fInClipPlane.isInitialized());
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400138 fragBuilder->codeAppend("half3 unionPlane;");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700139 varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane, "unionPlane");
140 }
Brian Salomon45c92202018-04-10 10:53:58 -0400141 GrGLSLVarying capRadius(kFloat_GrSLType);
Brian Salomon92be2f72018-06-19 14:33:47 -0400142 if (cgp.fInRoundCapCenters.isInitialized()) {
Brian Salomon45c92202018-04-10 10:53:58 -0400143 fragBuilder->codeAppend("float4 roundCapCenters;");
144 varyingHandler->addPassThroughAttribute(cgp.fInRoundCapCenters, "roundCapCenters");
145 varyingHandler->addVarying("capRadius", &capRadius,
146 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
147 // This is the cap radius in normalized space where the outer radius is 1 and
148 // circledEdge.w is the normalized inner radius.
149 vertBuilder->codeAppendf("%s = (1.0 - %s.w) / 2.0;", capRadius.vsOut(),
Brian Salomon92be2f72018-06-19 14:33:47 -0400150 cgp.kInCircleEdge.name());
Brian Salomon45c92202018-04-10 10:53:58 -0400151 }
joshualittabb52a12015-01-13 15:02:10 -0800152
joshualittb8c241a2015-05-19 08:23:30 -0700153 // setup pass through color
Brian Salomon92be2f72018-06-19 14:33:47 -0400154 varyingHandler->addPassThroughAttribute(cgp.kInColor, args.fOutputColor);
joshualitt9b989322014-12-15 14:16:27 -0800155
joshualittabb52a12015-01-13 15:02:10 -0800156 // Setup position
Brian Salomon92be2f72018-06-19 14:33:47 -0400157 this->writeOutputPosition(vertBuilder, gpArgs, cgp.kInPosition.name());
joshualittabb52a12015-01-13 15:02:10 -0800158
159 // emit transforms
egdaniel7ea439b2015-12-03 09:20:44 -0800160 this->emitTransforms(vertBuilder,
egdaniel0eafe792015-11-20 14:01:22 -0800161 varyingHandler,
egdaniel7ea439b2015-12-03 09:20:44 -0800162 uniformHandler,
Brian Salomon92be2f72018-06-19 14:33:47 -0400163 cgp.kInPosition.asShaderVar(),
bsalomon31df31c2016-08-17 09:00:24 -0700164 cgp.fLocalMatrix,
bsalomona624bf32016-09-20 09:12:47 -0700165 args.fFPCoordTransformHandler);
joshualitt4973d9d2014-11-08 09:24:25 -0800166
Ethan Nicholas8aa45692017-09-20 11:24:15 -0400167 fragBuilder->codeAppend("float d = length(circleEdge.xy);");
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400168 fragBuilder->codeAppend("half distanceToOuterEdge = circleEdge.z * (1.0 - d);");
169 fragBuilder->codeAppend("half edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);");
bsalomoncdaa97b2016-03-08 08:30:14 -0800170 if (cgp.fStroke) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500171 fragBuilder->codeAppend(
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400172 "half distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);");
173 fragBuilder->codeAppend("half innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);");
egdaniel4ca2e602015-11-18 08:01:26 -0800174 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000175 }
commit-bot@chromium.org0a6cb602013-04-11 15:05:37 +0000176
Brian Salomon92be2f72018-06-19 14:33:47 -0400177 if (cgp.fInClipPlane.isInitialized()) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500178 fragBuilder->codeAppend(
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400179 "half clip = clamp(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + "
Brian Salomon289e3d82016-12-14 15:52:56 -0500180 "clipPlane.z, 0.0, 1.0);");
Brian Salomon92be2f72018-06-19 14:33:47 -0400181 if (cgp.fInIsectPlane.isInitialized()) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500182 fragBuilder->codeAppend(
183 "clip *= clamp(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + "
184 "isectPlane.z, 0.0, 1.0);");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700185 }
Brian Salomon92be2f72018-06-19 14:33:47 -0400186 if (cgp.fInUnionPlane.isInitialized()) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500187 fragBuilder->codeAppend(
188 "clip += (1.0 - clip)*clamp(circleEdge.z * dot(circleEdge.xy, "
189 "unionPlane.xy) + unionPlane.z, 0.0, 1.0);");
bsalomon4f3a0ca2016-08-22 13:14:26 -0700190 }
191 fragBuilder->codeAppend("edgeAlpha *= clip;");
Brian Salomon92be2f72018-06-19 14:33:47 -0400192 if (cgp.fInRoundCapCenters.isInitialized()) {
Brian Salomon33c44222018-04-19 08:44:21 -0400193 // We compute coverage of the round caps as circles at the butt caps produced
194 // by the clip planes. The inverse of the clip planes is applied so that there
195 // is no double counting.
Brian Salomon45c92202018-04-10 10:53:58 -0400196 fragBuilder->codeAppendf(
197 "half dcap1 = circleEdge.z * (%s - length(circleEdge.xy - "
198 " roundCapCenters.xy));"
199 "half dcap2 = circleEdge.z * (%s - length(circleEdge.xy - "
200 " roundCapCenters.zw));"
Brian Salomon33c44222018-04-19 08:44:21 -0400201 "half capAlpha = (1 - clip) * (max(dcap1, 0) + max(dcap2, 0));"
Brian Salomon45c92202018-04-10 10:53:58 -0400202 "edgeAlpha = min(edgeAlpha + capAlpha, 1.0);",
203 capRadius.fsIn(), capRadius.fsIn());
204 }
bsalomon4f3a0ca2016-08-22 13:14:26 -0700205 }
Brian Salomonea26d6b2018-01-23 20:33:21 +0000206 fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000207 }
208
robertphillips46d36f02015-01-18 08:14:14 -0800209 static void GenKey(const GrGeometryProcessor& gp,
Brian Salomon94efbf52016-11-29 13:43:05 -0500210 const GrShaderCaps&,
joshualittb0a8a372014-09-23 09:50:21 -0700211 GrProcessorKeyBuilder* b) {
bsalomoncdaa97b2016-03-08 08:30:14 -0800212 const CircleGeometryProcessor& cgp = gp.cast<CircleGeometryProcessor>();
bsalomon4f3a0ca2016-08-22 13:14:26 -0700213 uint16_t key;
Brian Salomon289e3d82016-12-14 15:52:56 -0500214 key = cgp.fStroke ? 0x01 : 0x0;
bsalomon4f3a0ca2016-08-22 13:14:26 -0700215 key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0;
Brian Salomon92be2f72018-06-19 14:33:47 -0400216 key |= cgp.fInClipPlane.isInitialized() ? 0x04 : 0x0;
217 key |= cgp.fInIsectPlane.isInitialized() ? 0x08 : 0x0;
218 key |= cgp.fInUnionPlane.isInitialized() ? 0x10 : 0x0;
219 key |= cgp.fInRoundCapCenters.isInitialized() ? 0x20 : 0x0;
joshualittb8c241a2015-05-19 08:23:30 -0700220 b->add32(key);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000221 }
222
bsalomona624bf32016-09-20 09:12:47 -0700223 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
224 FPCoordTransformIter&& transformIter) override {
bsalomone4f24612016-08-17 10:30:17 -0700225 this->setTransformDataHelper(primProc.cast<CircleGeometryProcessor>().fLocalMatrix,
bsalomona624bf32016-09-20 09:12:47 -0700226 pdman, &transformIter);
joshualitte3ababe2015-05-15 07:56:07 -0700227 }
228
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000229 private:
egdaniele659a582015-11-13 09:55:43 -0800230 typedef GrGLSLGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000231 };
232
Brian Salomon92be2f72018-06-19 14:33:47 -0400233 const Attribute& onVertexAttribute(int i) const override {
234 return IthInitializedAttribute(i, kInPosition, kInColor, kInCircleEdge, fInClipPlane,
235 fInIsectPlane, fInUnionPlane, fInRoundCapCenters);
236 }
237
Brian Salomon289e3d82016-12-14 15:52:56 -0500238 SkMatrix fLocalMatrix;
Brian Salomon92be2f72018-06-19 14:33:47 -0400239
240 static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
241 static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
242 static constexpr Attribute kInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType};
243
244 // Optional attributes.
245 Attribute fInClipPlane;
246 Attribute fInIsectPlane;
247 Attribute fInUnionPlane;
248 Attribute fInRoundCapCenters;
249
Brian Salomon289e3d82016-12-14 15:52:56 -0500250 bool fStroke;
Brian Salomon0c26a9d2017-07-06 10:09:38 -0400251 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000252
joshualitt249af152014-09-15 11:41:13 -0700253 typedef GrGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000254};
Brian Salomon92be2f72018-06-19 14:33:47 -0400255constexpr GrPrimitiveProcessor::Attribute CircleGeometryProcessor::kInPosition;
256constexpr GrPrimitiveProcessor::Attribute CircleGeometryProcessor::kInColor;
257constexpr GrPrimitiveProcessor::Attribute CircleGeometryProcessor::kInCircleEdge;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000258
bsalomoncdaa97b2016-03-08 08:30:14 -0800259GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000260
Hal Canary6f6961e2017-01-31 13:50:44 -0500261#if GR_TEST_UTILS
bungeman06ca8ec2016-06-09 08:01:03 -0700262sk_sp<GrGeometryProcessor> CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Salomon45c92202018-04-10 10:53:58 -0400263 bool stroke = d->fRandom->nextBool();
264 bool roundCaps = stroke ? d->fRandom->nextBool() : false;
265 bool clipPlane = d->fRandom->nextBool();
266 bool isectPlane = d->fRandom->nextBool();
267 bool unionPlane = d->fRandom->nextBool();
268 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
269 return sk_sp<GrGeometryProcessor>(new CircleGeometryProcessor(stroke, roundCaps, clipPlane,
270 isectPlane, unionPlane, matrix));
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000271}
Hal Canary6f6961e2017-01-31 13:50:44 -0500272#endif
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000273
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400274class ButtCapDashedCircleGeometryProcessor : public GrGeometryProcessor {
275public:
276 ButtCapDashedCircleGeometryProcessor(const SkMatrix& localMatrix)
277 : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID), fLocalMatrix(localMatrix) {
Brian Salomon92be2f72018-06-19 14:33:47 -0400278 this->setVertexAttributeCnt(4);
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400279 }
280
281 ~ButtCapDashedCircleGeometryProcessor() override {}
282
283 const char* name() const override { return "ButtCapDashedCircleGeometryProcessor"; }
284
285 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
286 GLSLProcessor::GenKey(*this, caps, b);
287 }
288
289 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
290 return new GLSLProcessor();
291 }
292
293private:
294 class GLSLProcessor : public GrGLSLGeometryProcessor {
295 public:
296 GLSLProcessor() {}
297
298 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
299 const ButtCapDashedCircleGeometryProcessor& bcscgp =
300 args.fGP.cast<ButtCapDashedCircleGeometryProcessor>();
301 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
302 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
303 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
304 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
305
306 // emit attributes
307 varyingHandler->emitAttributes(bcscgp);
308 fragBuilder->codeAppend("float4 circleEdge;");
Brian Salomon92be2f72018-06-19 14:33:47 -0400309 varyingHandler->addPassThroughAttribute(bcscgp.kInCircleEdge, "circleEdge");
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400310
311 fragBuilder->codeAppend("float4 dashParams;");
312 varyingHandler->addPassThroughAttribute(
Brian Salomon92be2f72018-06-19 14:33:47 -0400313 bcscgp.kInDashParams, "dashParams",
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400314 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
315 GrGLSLVarying wrapDashes(kHalf4_GrSLType);
316 varyingHandler->addVarying("wrapDashes", &wrapDashes,
317 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
318 GrGLSLVarying lastIntervalLength(kHalf_GrSLType);
319 varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength,
320 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
Brian Salomon92be2f72018-06-19 14:33:47 -0400321 vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.kInDashParams.name());
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400322 // Our fragment shader works in on/off intervals as specified by dashParams.xy:
323 // x = length of on interval, y = length of on + off.
324 // There are two other parameters in dashParams.zw:
325 // z = start angle in radians, w = phase offset in radians in range -y/2..y/2.
326 // Each interval has a "corresponding" dash which may be shifted partially or
327 // fully out of its interval by the phase. So there may be up to two "visual"
328 // dashes in an interval.
329 // When computing coverage in an interval we look at three dashes. These are the
330 // "corresponding" dashes from the current, previous, and next intervals. Any of these
331 // may be phase shifted into our interval or even when phase=0 they may be within half a
332 // pixel distance of a pixel center in the interval.
333 // When in the first interval we need to check the dash from the last interval. And
334 // similarly when in the last interval we need to check the dash from the first
335 // interval. When 2pi is not perfectly divisible dashParams.y this is a boundary case.
336 // We compute the dash begin/end angles in the vertex shader and apply them in the
337 // fragment shader when we detect we're in the first/last interval.
338 vertBuilder->codeAppend(R"(
339 // The two boundary dash intervals are stored in wrapDashes.xy and .zw and fed
340 // to the fragment shader as a varying.
341 float4 wrapDashes;
342 half lastIntervalLength = mod(6.28318530718, dashParams.y);
343 // We can happen to be perfectly divisible.
344 if (0 == lastIntervalLength) {
345 lastIntervalLength = dashParams.y;
346 }
347 // Let 'l' be the last interval before reaching 2 pi.
348 // Based on the phase determine whether (l-1)th, l-th, or (l+1)th interval's
349 // "corresponding" dash appears in the l-th interval and is closest to the 0-th
350 // interval.
351 half offset = 0;
352 if (-dashParams.w >= lastIntervalLength) {
353 offset = -dashParams.y;
354 } else if (dashParams.w > dashParams.y - lastIntervalLength) {
355 offset = dashParams.y;
356 }
357 wrapDashes.x = -lastIntervalLength + offset - dashParams.w;
358 // The end of this dash may be beyond the 2 pi and therefore clipped. Hence the
359 // min.
360 wrapDashes.y = min(wrapDashes.x + dashParams.x, 0);
361
362 // Based on the phase determine whether the -1st, 0th, or 1st interval's
363 // "corresponding" dash appears in the 0th interval and is closest to l.
364 offset = 0;
365 if (dashParams.w >= dashParams.x) {
366 offset = dashParams.y;
367 } else if (-dashParams.w > dashParams.y - dashParams.x) {
368 offset = -dashParams.y;
369 }
370 wrapDashes.z = lastIntervalLength + offset - dashParams.w;
371 wrapDashes.w = wrapDashes.z + dashParams.x;
372 // The start of the dash we're considering may be clipped by the start of the
373 // circle.
374 wrapDashes.z = max(wrapDashes.z, lastIntervalLength);
375 )");
376 vertBuilder->codeAppendf("%s = wrapDashes;", wrapDashes.vsOut());
377 vertBuilder->codeAppendf("%s = lastIntervalLength;", lastIntervalLength.vsOut());
378 fragBuilder->codeAppendf("half4 wrapDashes = %s;", wrapDashes.fsIn());
379 fragBuilder->codeAppendf("half lastIntervalLength = %s;", lastIntervalLength.fsIn());
380
381 // setup pass through color
382 varyingHandler->addPassThroughAttribute(
Brian Salomon92be2f72018-06-19 14:33:47 -0400383 bcscgp.kInColor, args.fOutputColor,
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400384 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
385
386 // Setup position
Brian Salomon92be2f72018-06-19 14:33:47 -0400387 this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.kInPosition.name());
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400388
389 // emit transforms
390 this->emitTransforms(vertBuilder,
391 varyingHandler,
392 uniformHandler,
Brian Salomon92be2f72018-06-19 14:33:47 -0400393 bcscgp.kInPosition.asShaderVar(),
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400394 bcscgp.fLocalMatrix,
395 args.fFPCoordTransformHandler);
396 GrShaderVar fnArgs[] = {
397 GrShaderVar("angleToEdge", kFloat_GrSLType),
398 GrShaderVar("diameter", kFloat_GrSLType),
399 };
400 SkString fnName;
401 fragBuilder->emitFunction(kFloat_GrSLType, "coverage_from_dash_edge",
402 SK_ARRAY_COUNT(fnArgs), fnArgs, R"(
403 float linearDist;
404 angleToEdge = clamp(angleToEdge, -3.1415, 3.1415);
405 linearDist = diameter * sin(angleToEdge / 2);
406 return clamp(linearDist + 0.5, 0, 1);
407 )",
408 &fnName);
409 fragBuilder->codeAppend(R"(
410 float d = length(circleEdge.xy) * circleEdge.z;
411
412 // Compute coverage from outer/inner edges of the stroke.
413 half distanceToOuterEdge = circleEdge.z - d;
414 half edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
415 half distanceToInnerEdge = d - circleEdge.z * circleEdge.w;
416 half innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);
417 edgeAlpha *= innerAlpha;
418
419 half angleFromStart = atan(circleEdge.y, circleEdge.x) - dashParams.z;
420 angleFromStart = mod(angleFromStart, 6.28318530718);
421 float x = mod(angleFromStart, dashParams.y);
422 // Convert the radial distance from center to pixel into a diameter.
423 d *= 2;
424 half2 currDash = half2(-dashParams.w, dashParams.x - dashParams.w);
425 half2 nextDash = half2(dashParams.y - dashParams.w,
426 dashParams.y + dashParams.x - dashParams.w);
427 half2 prevDash = half2(-dashParams.y - dashParams.w,
428 -dashParams.y + dashParams.x - dashParams.w);
429 half dashAlpha = 0;
430 )");
431 fragBuilder->codeAppendf(R"(
432 if (angleFromStart - x + dashParams.y >= 6.28318530718) {
433 dashAlpha += %s(x - wrapDashes.z, d) * %s(wrapDashes.w - x, d);
434 currDash.y = min(currDash.y, lastIntervalLength);
435 if (nextDash.x >= lastIntervalLength) {
436 // The next dash is outside the 0..2pi range, throw it away
437 nextDash.xy = half2(1000);
438 } else {
439 // Clip the end of the next dash to the end of the circle
440 nextDash.y = min(nextDash.y, lastIntervalLength);
441 }
442 }
443 )", fnName.c_str(), fnName.c_str());
444 fragBuilder->codeAppendf(R"(
445 if (angleFromStart - x - dashParams.y < -0.01) {
446 dashAlpha += %s(x - wrapDashes.x, d) * %s(wrapDashes.y - x, d);
447 currDash.x = max(currDash.x, 0);
448 if (prevDash.y <= 0) {
449 // The previous dash is outside the 0..2pi range, throw it away
450 prevDash.xy = half2(1000);
451 } else {
452 // Clip the start previous dash to the start of the circle
453 prevDash.x = max(prevDash.x, 0);
454 }
455 }
456 )", fnName.c_str(), fnName.c_str());
457 fragBuilder->codeAppendf(R"(
458 dashAlpha += %s(x - currDash.x, d) * %s(currDash.y - x, d);
459 dashAlpha += %s(x - nextDash.x, d) * %s(nextDash.y - x, d);
460 dashAlpha += %s(x - prevDash.x, d) * %s(prevDash.y - x, d);
461 dashAlpha = min(dashAlpha, 1);
462 edgeAlpha *= dashAlpha;
463 )", fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(),
464 fnName.c_str());
465 fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
466 }
467
468 static void GenKey(const GrGeometryProcessor& gp,
469 const GrShaderCaps&,
470 GrProcessorKeyBuilder* b) {
471 const ButtCapDashedCircleGeometryProcessor& bcscgp =
472 gp.cast<ButtCapDashedCircleGeometryProcessor>();
473 b->add32(bcscgp.fLocalMatrix.hasPerspective());
474 }
475
476 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
477 FPCoordTransformIter&& transformIter) override {
478 this->setTransformDataHelper(
479 primProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix, pdman,
480 &transformIter);
481 }
482
483 private:
484 typedef GrGLSLGeometryProcessor INHERITED;
485 };
486
Brian Salomon92be2f72018-06-19 14:33:47 -0400487 const Attribute& onVertexAttribute(int i) const override {
488 return IthAttribute(i, kInPosition, kInColor, kInCircleEdge, kInDashParams);
489 }
490
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400491 SkMatrix fLocalMatrix;
Brian Salomon92be2f72018-06-19 14:33:47 -0400492 static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
493 static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
494 static constexpr Attribute kInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType};
495 static constexpr Attribute kInDashParams = {"inDashParams", kFloat4_GrVertexAttribType};
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400496
497 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
498
499 typedef GrGeometryProcessor INHERITED;
500};
Brian Salomon92be2f72018-06-19 14:33:47 -0400501constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInPosition;
502constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInColor;
503constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInCircleEdge;
504constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInDashParams;
Brian Salomon62e4f3d2018-04-20 13:54:11 -0400505
506#if GR_TEST_UTILS
507sk_sp<GrGeometryProcessor> ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
508 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
509 return sk_sp<GrGeometryProcessor>(new ButtCapDashedCircleGeometryProcessor(matrix));
510}
511#endif
512
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000513///////////////////////////////////////////////////////////////////////////////
514
515/**
516 * 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 +0000517 * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii,
518 * in both x and y directions.
519 *
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +0000520 * 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 +0000521 */
522
bsalomoncdaa97b2016-03-08 08:30:14 -0800523class EllipseGeometryProcessor : public GrGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000524public:
Brian Salomonea26d6b2018-01-23 20:33:21 +0000525 EllipseGeometryProcessor(bool stroke, const SkMatrix& localMatrix)
526 : INHERITED(kEllipseGeometryProcessor_ClassID)
527 , fLocalMatrix(localMatrix) {
Brian Salomon92be2f72018-06-19 14:33:47 -0400528 this->setVertexAttributeCnt(4);
Brian Salomonea26d6b2018-01-23 20:33:21 +0000529 fStroke = stroke;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000530 }
531
Brian Salomond3b65972017-03-22 12:05:03 -0400532 ~EllipseGeometryProcessor() override {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000533
mtklein36352bf2015-03-25 18:17:31 -0700534 const char* name() const override { return "EllipseEdge"; }
joshualitt2dd1ae02014-12-03 06:24:10 -0800535
Brian Salomon94efbf52016-11-29 13:43:05 -0500536 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700537 GLSLProcessor::GenKey(*this, caps, b);
538 }
539
Brian Salomon94efbf52016-11-29 13:43:05 -0500540 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700541 return new GLSLProcessor();
542 }
543
544private:
egdaniel57d3b032015-11-13 11:57:27 -0800545 class GLSLProcessor : public GrGLSLGeometryProcessor {
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000546 public:
brianosmanbb2ff942016-02-11 14:15:18 -0800547 GLSLProcessor() {}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000548
Brian Salomon289e3d82016-12-14 15:52:56 -0500549 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800550 const EllipseGeometryProcessor& egp = args.fGP.cast<EllipseGeometryProcessor>();
egdaniel4ca2e602015-11-18 08:01:26 -0800551 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
egdaniel0eafe792015-11-20 14:01:22 -0800552 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
egdaniel7ea439b2015-12-03 09:20:44 -0800553 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000554
joshualittabb52a12015-01-13 15:02:10 -0800555 // emit attributes
bsalomoncdaa97b2016-03-08 08:30:14 -0800556 varyingHandler->emitAttributes(egp);
joshualittabb52a12015-01-13 15:02:10 -0800557
Chris Dalton27372882017-12-08 13:34:21 -0700558 GrGLSLVarying ellipseOffsets(kHalf2_GrSLType);
egdaniel0eafe792015-11-20 14:01:22 -0800559 varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets);
egdaniel4ca2e602015-11-18 08:01:26 -0800560 vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(),
Brian Salomon92be2f72018-06-19 14:33:47 -0400561 egp.kInEllipseOffset.name());
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000562
Chris Dalton27372882017-12-08 13:34:21 -0700563 GrGLSLVarying ellipseRadii(kHalf4_GrSLType);
egdaniel0eafe792015-11-20 14:01:22 -0800564 varyingHandler->addVarying("EllipseRadii", &ellipseRadii);
Brian Salomon92be2f72018-06-19 14:33:47 -0400565 vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.kInEllipseRadii.name());
joshualitt2dd1ae02014-12-03 06:24:10 -0800566
Chris Dalton60283612018-02-14 13:38:14 -0700567 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
joshualittb8c241a2015-05-19 08:23:30 -0700568 // setup pass through color
Brian Salomon92be2f72018-06-19 14:33:47 -0400569 varyingHandler->addPassThroughAttribute(egp.kInColor, args.fOutputColor);
joshualitt9b989322014-12-15 14:16:27 -0800570
joshualittabb52a12015-01-13 15:02:10 -0800571 // Setup position
Brian Salomon92be2f72018-06-19 14:33:47 -0400572 this->writeOutputPosition(vertBuilder, gpArgs, egp.kInPosition.name());
joshualittabb52a12015-01-13 15:02:10 -0800573
574 // emit transforms
egdaniel7ea439b2015-12-03 09:20:44 -0800575 this->emitTransforms(vertBuilder,
egdaniel0eafe792015-11-20 14:01:22 -0800576 varyingHandler,
egdaniel7ea439b2015-12-03 09:20:44 -0800577 uniformHandler,
Brian Salomon92be2f72018-06-19 14:33:47 -0400578 egp.kInPosition.asShaderVar(),
bsalomon31df31c2016-08-17 09:00:24 -0700579 egp.fLocalMatrix,
bsalomona624bf32016-09-20 09:12:47 -0700580 args.fFPCoordTransformHandler);
joshualitt4973d9d2014-11-08 09:24:25 -0800581
skia.committer@gmail.com8be02fc2013-05-17 07:01:11 +0000582 // for outer curve
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400583 fragBuilder->codeAppendf("half2 scaledOffset = %s*%s.xy;", ellipseOffsets.fsIn(),
egdaniel4ca2e602015-11-18 08:01:26 -0800584 ellipseRadii.fsIn());
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400585 fragBuilder->codeAppend("half test = dot(scaledOffset, scaledOffset) - 1.0;");
586 fragBuilder->codeAppendf("half2 grad = 2.0*scaledOffset*%s.xy;", ellipseRadii.fsIn());
587 fragBuilder->codeAppend("half grad_dot = dot(grad, grad);");
joshualitt74077b92014-10-24 11:26:03 -0700588
commit-bot@chromium.org1b035d82014-04-09 17:11:09 +0000589 // avoid calling inversesqrt on zero.
egdaniel4ca2e602015-11-18 08:01:26 -0800590 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);");
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400591 fragBuilder->codeAppend("half invlen = inversesqrt(grad_dot);");
592 fragBuilder->codeAppend("half edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000593
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +0000594 // for inner curve
bsalomoncdaa97b2016-03-08 08:30:14 -0800595 if (egp.fStroke) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500596 fragBuilder->codeAppendf("scaledOffset = %s*%s.zw;", ellipseOffsets.fsIn(),
egdaniel4ca2e602015-11-18 08:01:26 -0800597 ellipseRadii.fsIn());
Brian Salomon289e3d82016-12-14 15:52:56 -0500598 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;");
599 fragBuilder->codeAppendf("grad = 2.0*scaledOffset*%s.zw;", ellipseRadii.fsIn());
egdaniel4ca2e602015-11-18 08:01:26 -0800600 fragBuilder->codeAppend("invlen = inversesqrt(dot(grad, grad));");
601 fragBuilder->codeAppend("edgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000602 }
603
Brian Salomonea26d6b2018-01-23 20:33:21 +0000604 fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000605 }
606
robertphillips46d36f02015-01-18 08:14:14 -0800607 static void GenKey(const GrGeometryProcessor& gp,
Brian Salomon94efbf52016-11-29 13:43:05 -0500608 const GrShaderCaps&,
joshualittb0a8a372014-09-23 09:50:21 -0700609 GrProcessorKeyBuilder* b) {
bsalomoncdaa97b2016-03-08 08:30:14 -0800610 const EllipseGeometryProcessor& egp = gp.cast<EllipseGeometryProcessor>();
611 uint16_t key = egp.fStroke ? 0x1 : 0x0;
bsalomon31df31c2016-08-17 09:00:24 -0700612 key |= egp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0;
joshualittb8c241a2015-05-19 08:23:30 -0700613 b->add32(key);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000614 }
615
bsalomona624bf32016-09-20 09:12:47 -0700616 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
617 FPCoordTransformIter&& transformIter) override {
618 const EllipseGeometryProcessor& egp = primProc.cast<EllipseGeometryProcessor>();
619 this->setTransformDataHelper(egp.fLocalMatrix, pdman, &transformIter);
joshualitte3ababe2015-05-15 07:56:07 -0700620 }
621
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000622 private:
egdaniele659a582015-11-13 09:55:43 -0800623 typedef GrGLSLGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000624 };
625
Brian Salomon92be2f72018-06-19 14:33:47 -0400626 const Attribute& onVertexAttribute(int i) const override {
627 return IthAttribute(i, kInPosition, kInColor, kInEllipseOffset, kInEllipseRadii);
628 }
629
630 static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
631 static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
632 static constexpr Attribute kInEllipseOffset = {"inEllipseOffset", kHalf2_GrVertexAttribType};
633 static constexpr Attribute kInEllipseRadii = {"inEllipseRadii", kHalf4_GrVertexAttribType};
634
joshualitte3ababe2015-05-15 07:56:07 -0700635 SkMatrix fLocalMatrix;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000636 bool fStroke;
637
Brian Salomon0c26a9d2017-07-06 10:09:38 -0400638 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000639
joshualitt249af152014-09-15 11:41:13 -0700640 typedef GrGeometryProcessor INHERITED;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000641};
Brian Salomon92be2f72018-06-19 14:33:47 -0400642constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInPosition;
643constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInColor;
644constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInEllipseOffset;
645constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInEllipseRadii;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000646
bsalomoncdaa97b2016-03-08 08:30:14 -0800647GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000648
Hal Canary6f6961e2017-01-31 13:50:44 -0500649#if GR_TEST_UTILS
bungeman06ca8ec2016-06-09 08:01:03 -0700650sk_sp<GrGeometryProcessor> EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Salomonea26d6b2018-01-23 20:33:21 +0000651 return sk_sp<GrGeometryProcessor>(
652 new EllipseGeometryProcessor(d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom)));
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000653}
Hal Canary6f6961e2017-01-31 13:50:44 -0500654#endif
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000655
656///////////////////////////////////////////////////////////////////////////////
657
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000658/**
skia.committer@gmail.com6fc1b492013-09-06 07:01:45 +0000659 * 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 +0000660 * specified as a 2D offset from center for both the outer and inner paths (if stroked). The
661 * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by
662 * using differentials.
663 *
664 * The result is device-independent and can be used with any affine matrix.
665 */
666
bsalomoncdaa97b2016-03-08 08:30:14 -0800667enum class DIEllipseStyle { kStroke = 0, kHairline, kFill };
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000668
bsalomoncdaa97b2016-03-08 08:30:14 -0800669class DIEllipseGeometryProcessor : public GrGeometryProcessor {
670public:
Brian Salomonea26d6b2018-01-23 20:33:21 +0000671 DIEllipseGeometryProcessor(const SkMatrix& viewMatrix, DIEllipseStyle style)
Ethan Nicholasabff9562017-10-09 10:54:08 -0400672 : INHERITED(kDIEllipseGeometryProcessor_ClassID)
Brian Salomonea26d6b2018-01-23 20:33:21 +0000673 , fViewMatrix(viewMatrix) {
Brian Salomonea26d6b2018-01-23 20:33:21 +0000674 fStyle = style;
Brian Salomon92be2f72018-06-19 14:33:47 -0400675 this->setVertexAttributeCnt(4);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000676 }
677
Brian Salomond3b65972017-03-22 12:05:03 -0400678 ~DIEllipseGeometryProcessor() override {}
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000679
mtklein36352bf2015-03-25 18:17:31 -0700680 const char* name() const override { return "DIEllipseEdge"; }
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000681
Brian Salomon94efbf52016-11-29 13:43:05 -0500682 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700683 GLSLProcessor::GenKey(*this, caps, b);
684 }
halcanary9d524f22016-03-29 09:03:52 -0700685
Brian Salomon94efbf52016-11-29 13:43:05 -0500686 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
bsalomon31df31c2016-08-17 09:00:24 -0700687 return new GLSLProcessor();
688 }
689
690private:
egdaniel57d3b032015-11-13 11:57:27 -0800691 class GLSLProcessor : public GrGLSLGeometryProcessor {
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000692 public:
Brian Salomon289e3d82016-12-14 15:52:56 -0500693 GLSLProcessor() : fViewMatrix(SkMatrix::InvalidMatrix()) {}
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000694
joshualitt465283c2015-09-11 08:19:35 -0700695 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800696 const DIEllipseGeometryProcessor& diegp = args.fGP.cast<DIEllipseGeometryProcessor>();
egdaniel4ca2e602015-11-18 08:01:26 -0800697 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
egdaniel0eafe792015-11-20 14:01:22 -0800698 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
egdaniel7ea439b2015-12-03 09:20:44 -0800699 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000700
joshualittabb52a12015-01-13 15:02:10 -0800701 // emit attributes
bsalomoncdaa97b2016-03-08 08:30:14 -0800702 varyingHandler->emitAttributes(diegp);
joshualittabb52a12015-01-13 15:02:10 -0800703
Chris Dalton27372882017-12-08 13:34:21 -0700704 GrGLSLVarying offsets0(kHalf2_GrSLType);
egdaniel0eafe792015-11-20 14:01:22 -0800705 varyingHandler->addVarying("EllipseOffsets0", &offsets0);
Brian Salomon92be2f72018-06-19 14:33:47 -0400706 vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.kInEllipseOffsets0.name());
joshualitt74077b92014-10-24 11:26:03 -0700707
Chris Dalton27372882017-12-08 13:34:21 -0700708 GrGLSLVarying offsets1(kHalf2_GrSLType);
egdaniel0eafe792015-11-20 14:01:22 -0800709 varyingHandler->addVarying("EllipseOffsets1", &offsets1);
Brian Salomon92be2f72018-06-19 14:33:47 -0400710 vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.kInEllipseOffsets1.name());
joshualitt2dd1ae02014-12-03 06:24:10 -0800711
Chris Dalton60283612018-02-14 13:38:14 -0700712 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
Brian Salomon92be2f72018-06-19 14:33:47 -0400713 varyingHandler->addPassThroughAttribute(diegp.kInColor, args.fOutputColor);
joshualitt9b989322014-12-15 14:16:27 -0800714
joshualittabb52a12015-01-13 15:02:10 -0800715 // Setup position
Brian Salomon7f235432017-08-16 09:41:48 -0400716 this->writeOutputPosition(vertBuilder,
717 uniformHandler,
718 gpArgs,
Brian Salomon92be2f72018-06-19 14:33:47 -0400719 diegp.kInPosition.name(),
Brian Salomon7f235432017-08-16 09:41:48 -0400720 diegp.fViewMatrix,
721 &fViewMatrixUniform);
joshualittabb52a12015-01-13 15:02:10 -0800722
723 // emit transforms
egdaniel7ea439b2015-12-03 09:20:44 -0800724 this->emitTransforms(vertBuilder,
egdaniel0eafe792015-11-20 14:01:22 -0800725 varyingHandler,
egdaniel7ea439b2015-12-03 09:20:44 -0800726 uniformHandler,
Brian Salomon92be2f72018-06-19 14:33:47 -0400727 diegp.kInPosition.asShaderVar(),
bsalomona624bf32016-09-20 09:12:47 -0700728 args.fFPCoordTransformHandler);
joshualitt4973d9d2014-11-08 09:24:25 -0800729
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000730 // for outer curve
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400731 fragBuilder->codeAppendf("half2 scaledOffset = %s.xy;", offsets0.fsIn());
732 fragBuilder->codeAppend("half test = dot(scaledOffset, scaledOffset) - 1.0;");
733 fragBuilder->codeAppendf("half2 duvdx = dFdx(%s);", offsets0.fsIn());
734 fragBuilder->codeAppendf("half2 duvdy = dFdy(%s);", offsets0.fsIn());
Brian Salomon289e3d82016-12-14 15:52:56 -0500735 fragBuilder->codeAppendf(
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400736 "half2 grad = half2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y,"
737 " 2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);",
Brian Salomon289e3d82016-12-14 15:52:56 -0500738 offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn());
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000739
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400740 fragBuilder->codeAppend("half grad_dot = dot(grad, grad);");
commit-bot@chromium.org1b035d82014-04-09 17:11:09 +0000741 // avoid calling inversesqrt on zero.
egdaniel4ca2e602015-11-18 08:01:26 -0800742 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);");
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400743 fragBuilder->codeAppend("half invlen = inversesqrt(grad_dot);");
bsalomoncdaa97b2016-03-08 08:30:14 -0800744 if (DIEllipseStyle::kHairline == diegp.fStyle) {
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000745 // can probably do this with one step
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400746 fragBuilder->codeAppend("half edgeAlpha = clamp(1.0-test*invlen, 0.0, 1.0);");
egdaniel4ca2e602015-11-18 08:01:26 -0800747 fragBuilder->codeAppend("edgeAlpha *= clamp(1.0+test*invlen, 0.0, 1.0);");
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000748 } else {
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400749 fragBuilder->codeAppend("half edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);");
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000750 }
751
752 // for inner curve
bsalomoncdaa97b2016-03-08 08:30:14 -0800753 if (DIEllipseStyle::kStroke == diegp.fStyle) {
egdaniel4ca2e602015-11-18 08:01:26 -0800754 fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn());
755 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;");
756 fragBuilder->codeAppendf("duvdx = dFdx(%s);", offsets1.fsIn());
757 fragBuilder->codeAppendf("duvdy = dFdy(%s);", offsets1.fsIn());
Brian Salomon289e3d82016-12-14 15:52:56 -0500758 fragBuilder->codeAppendf(
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400759 "grad = half2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y,"
760 " 2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);",
Brian Salomon289e3d82016-12-14 15:52:56 -0500761 offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn());
egdaniel4ca2e602015-11-18 08:01:26 -0800762 fragBuilder->codeAppend("invlen = inversesqrt(dot(grad, grad));");
763 fragBuilder->codeAppend("edgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);");
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000764 }
Brian Salomonea26d6b2018-01-23 20:33:21 +0000765
766 fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000767 }
768
robertphillips46d36f02015-01-18 08:14:14 -0800769 static void GenKey(const GrGeometryProcessor& gp,
Brian Salomon94efbf52016-11-29 13:43:05 -0500770 const GrShaderCaps&,
joshualittb0a8a372014-09-23 09:50:21 -0700771 GrProcessorKeyBuilder* b) {
bsalomoncdaa97b2016-03-08 08:30:14 -0800772 const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
773 uint16_t key = static_cast<uint16_t>(diegp.fStyle);
bsalomon31df31c2016-08-17 09:00:24 -0700774 key |= ComputePosKey(diegp.fViewMatrix) << 10;
joshualittb8c241a2015-05-19 08:23:30 -0700775 b->add32(key);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000776 }
777
bsalomona624bf32016-09-20 09:12:47 -0700778 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
779 FPCoordTransformIter&& transformIter) override {
bsalomoncdaa97b2016-03-08 08:30:14 -0800780 const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
joshualitt5559ca22015-05-21 15:50:36 -0700781
bsalomon31df31c2016-08-17 09:00:24 -0700782 if (!diegp.fViewMatrix.isIdentity() && !fViewMatrix.cheapEqualTo(diegp.fViewMatrix)) {
783 fViewMatrix = diegp.fViewMatrix;
egdaniel018fb622015-10-28 07:26:40 -0700784 float viewMatrix[3 * 3];
egdaniel64c47282015-11-13 06:54:19 -0800785 GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
joshualitt5559ca22015-05-21 15:50:36 -0700786 pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
787 }
bsalomona624bf32016-09-20 09:12:47 -0700788 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000789 }
790
791 private:
joshualitt5559ca22015-05-21 15:50:36 -0700792 SkMatrix fViewMatrix;
joshualitt5559ca22015-05-21 15:50:36 -0700793 UniformHandle fViewMatrixUniform;
joshualitt9b989322014-12-15 14:16:27 -0800794
egdaniele659a582015-11-13 09:55:43 -0800795 typedef GrGLSLGeometryProcessor INHERITED;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000796 };
797
Brian Salomon92be2f72018-06-19 14:33:47 -0400798 const Attribute& onVertexAttribute(int i) const override {
799 return IthAttribute(i, kInPosition, kInColor, kInEllipseOffsets0, kInEllipseOffsets1);
800 }
801
802 static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
803 static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
804 static constexpr Attribute kInEllipseOffsets0 = {"inEllipseOffsets0",
805 kHalf2_GrVertexAttribType};
806 static constexpr Attribute kInEllipseOffsets1 = {"inEllipseOffsets1",
807 kHalf2_GrVertexAttribType};
808
Brian Salomon289e3d82016-12-14 15:52:56 -0500809 SkMatrix fViewMatrix;
810 DIEllipseStyle fStyle;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000811
Brian Salomon0c26a9d2017-07-06 10:09:38 -0400812 GR_DECLARE_GEOMETRY_PROCESSOR_TEST
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000813
joshualitt249af152014-09-15 11:41:13 -0700814 typedef GrGeometryProcessor INHERITED;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000815};
Brian Salomon92be2f72018-06-19 14:33:47 -0400816constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInPosition;
817constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInColor;
818constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInEllipseOffsets0;
819constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInEllipseOffsets1;
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000820
bsalomoncdaa97b2016-03-08 08:30:14 -0800821GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor);
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000822
Hal Canary6f6961e2017-01-31 13:50:44 -0500823#if GR_TEST_UTILS
bungeman06ca8ec2016-06-09 08:01:03 -0700824sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
Brian Salomon289e3d82016-12-14 15:52:56 -0500825 return sk_sp<GrGeometryProcessor>(new DIEllipseGeometryProcessor(
Brian Salomonea26d6b2018-01-23 20:33:21 +0000826 GrTest::TestMatrix(d->fRandom), (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2))));
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000827}
Hal Canary6f6961e2017-01-31 13:50:44 -0500828#endif
commit-bot@chromium.org5242ed72013-09-05 19:26:51 +0000829
830///////////////////////////////////////////////////////////////////////////////
831
jvanverth6ca48822016-10-07 06:57:32 -0700832// We have two possible cases for geometry for a circle:
833
834// In the case of a normal fill, we draw geometry for the circle as an octagon.
835static const uint16_t gFillCircleIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -0500836 // enter the octagon
837 // clang-format off
838 0, 1, 8, 1, 2, 8,
839 2, 3, 8, 3, 4, 8,
840 4, 5, 8, 5, 6, 8,
841 6, 7, 8, 7, 0, 8
842 // clang-format on
jvanverth6ca48822016-10-07 06:57:32 -0700843};
844
845// For stroked circles, we use two nested octagons.
846static const uint16_t gStrokeCircleIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -0500847 // enter the octagon
848 // clang-format off
849 0, 1, 9, 0, 9, 8,
850 1, 2, 10, 1, 10, 9,
851 2, 3, 11, 2, 11, 10,
852 3, 4, 12, 3, 12, 11,
853 4, 5, 13, 4, 13, 12,
854 5, 6, 14, 5, 14, 13,
855 6, 7, 15, 6, 15, 14,
856 7, 0, 8, 7, 8, 15,
857 // clang-format on
jvanverth6ca48822016-10-07 06:57:32 -0700858};
859
Brian Salomon289e3d82016-12-14 15:52:56 -0500860
jvanverth6ca48822016-10-07 06:57:32 -0700861static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
862static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
863static const int kVertsPerStrokeCircle = 16;
864static const int kVertsPerFillCircle = 9;
865
866static int circle_type_to_vert_count(bool stroked) {
867 return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
868}
869
870static int circle_type_to_index_count(bool stroked) {
871 return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
872}
873
874static const uint16_t* circle_type_to_indices(bool stroked) {
875 return stroked ? gStrokeCircleIndices : gFillCircleIndices;
876}
877
878///////////////////////////////////////////////////////////////////////////////
879
Brian Salomon05441c42017-05-15 16:45:49 -0400880class CircleOp final : public GrMeshDrawOp {
881private:
882 using Helper = GrSimpleMeshDrawOpHelper;
883
joshualitt76e7fb62015-02-11 08:52:27 -0800884public:
Brian Salomon25a88092016-12-01 09:36:50 -0500885 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -0700886
bsalomon4f3a0ca2016-08-22 13:14:26 -0700887 /** Optional extra params to render a partial arc rather than a full circle. */
888 struct ArcParams {
889 SkScalar fStartAngleRadians;
890 SkScalar fSweepAngleRadians;
891 bool fUseCenter;
892 };
Brian Salomon05441c42017-05-15 16:45:49 -0400893
Robert Phillips7c525e62018-06-12 10:11:12 -0400894 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
895 GrPaint&& paint,
896 const SkMatrix& viewMatrix,
897 SkPoint center,
898 SkScalar radius,
899 const GrStyle& style,
Brian Salomon05441c42017-05-15 16:45:49 -0400900 const ArcParams* arcParams = nullptr) {
bsalomon4f3a0ca2016-08-22 13:14:26 -0700901 SkASSERT(circle_stays_circle(viewMatrix));
bsalomon4f3a0ca2016-08-22 13:14:26 -0700902 if (style.hasPathEffect()) {
903 return nullptr;
904 }
Brian Salomon05441c42017-05-15 16:45:49 -0400905 const SkStrokeRec& stroke = style.strokeRec();
bsalomon4f3a0ca2016-08-22 13:14:26 -0700906 SkStrokeRec::Style recStyle = stroke.getStyle();
907 if (arcParams) {
908 // Arc support depends on the style.
909 switch (recStyle) {
910 case SkStrokeRec::kStrokeAndFill_Style:
Brian Salomon289e3d82016-12-14 15:52:56 -0500911 // This produces a strange result that this op doesn't implement.
bsalomon4f3a0ca2016-08-22 13:14:26 -0700912 return nullptr;
913 case SkStrokeRec::kFill_Style:
914 // This supports all fills.
915 break;
Brian Salomon45c92202018-04-10 10:53:58 -0400916 case SkStrokeRec::kStroke_Style:
917 // Strokes that don't use the center point are supported with butt and round
918 // caps.
919 if (arcParams->fUseCenter || stroke.getCap() == SkPaint::kSquare_Cap) {
920 return nullptr;
921 }
922 break;
bsalomon4f3a0ca2016-08-22 13:14:26 -0700923 case SkStrokeRec::kHairline_Style:
Brian Salomon45c92202018-04-10 10:53:58 -0400924 // Hairline only supports butt cap. Round caps could be emulated by slightly
925 // extending the angle range if we ever care to.
bsalomon4f3a0ca2016-08-22 13:14:26 -0700926 if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) {
927 return nullptr;
928 }
929 break;
930 }
931 }
Robert Phillips7c525e62018-06-12 10:11:12 -0400932 return Helper::FactoryHelper<CircleOp>(context, std::move(paint), viewMatrix, center,
933 radius, style, arcParams);
Brian Salomon05441c42017-05-15 16:45:49 -0400934 }
935
Brian Salomonea26d6b2018-01-23 20:33:21 +0000936 CircleOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkMatrix& viewMatrix,
Brian Salomon05441c42017-05-15 16:45:49 -0400937 SkPoint center, SkScalar radius, const GrStyle& style, const ArcParams* arcParams)
Brian Salomonea26d6b2018-01-23 20:33:21 +0000938 : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
Brian Salomon05441c42017-05-15 16:45:49 -0400939 const SkStrokeRec& stroke = style.strokeRec();
940 SkStrokeRec::Style recStyle = stroke.getStyle();
bsalomon4f3a0ca2016-08-22 13:14:26 -0700941
Brian Salomon45c92202018-04-10 10:53:58 -0400942 fRoundCaps = false;
943
bsalomon4b4a7cc2016-07-08 04:42:54 -0700944 viewMatrix.mapPoints(&center, 1);
bsalomon4f3a0ca2016-08-22 13:14:26 -0700945 radius = viewMatrix.mapRadius(radius);
bsalomon4b4a7cc2016-07-08 04:42:54 -0700946 SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
joshualitt76e7fb62015-02-11 08:52:27 -0800947
Brian Salomon289e3d82016-12-14 15:52:56 -0500948 bool isStrokeOnly =
949 SkStrokeRec::kStroke_Style == recStyle || SkStrokeRec::kHairline_Style == recStyle;
bsalomon4f3a0ca2016-08-22 13:14:26 -0700950 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
bsalomon4b4a7cc2016-07-08 04:42:54 -0700951
jvanverth6ca48822016-10-07 06:57:32 -0700952 SkScalar innerRadius = -SK_ScalarHalf;
bsalomon4b4a7cc2016-07-08 04:42:54 -0700953 SkScalar outerRadius = radius;
954 SkScalar halfWidth = 0;
955 if (hasStroke) {
956 if (SkScalarNearlyZero(strokeWidth)) {
957 halfWidth = SK_ScalarHalf;
958 } else {
959 halfWidth = SkScalarHalf(strokeWidth);
960 }
961
962 outerRadius += halfWidth;
963 if (isStrokeOnly) {
964 innerRadius = radius - halfWidth;
965 }
966 }
967
968 // The radii are outset for two reasons. First, it allows the shader to simply perform
969 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
970 // Second, the outer radius is used to compute the verts of the bounding box that is
971 // rendered and the outset ensures the box will cover all partially covered by the circle.
972 outerRadius += SK_ScalarHalf;
973 innerRadius -= SK_ScalarHalf;
jvanverth6ca48822016-10-07 06:57:32 -0700974 bool stroked = isStrokeOnly && innerRadius > 0.0f;
Brian Salomon05441c42017-05-15 16:45:49 -0400975 fViewMatrixIfUsingLocalCoords = viewMatrix;
bsalomon4b4a7cc2016-07-08 04:42:54 -0700976
bsalomon4f3a0ca2016-08-22 13:14:26 -0700977 // This makes every point fully inside the intersection plane.
978 static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f};
979 // This makes every point fully outside the union plane.
980 static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f};
Brian Salomon45c92202018-04-10 10:53:58 -0400981 static constexpr SkPoint kUnusedRoundCaps[] = {{1e10f, 1e10f}, {1e10f, 1e10f}};
bsalomon4f3a0ca2016-08-22 13:14:26 -0700982 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
983 center.fX + outerRadius, center.fY + outerRadius);
bsalomon4f3a0ca2016-08-22 13:14:26 -0700984 if (arcParams) {
985 // The shader operates in a space where the circle is translated to be centered at the
986 // origin. Here we compute points on the unit circle at the starting and ending angles.
987 SkPoint startPoint, stopPoint;
988 startPoint.fY = SkScalarSinCos(arcParams->fStartAngleRadians, &startPoint.fX);
989 SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians;
990 stopPoint.fY = SkScalarSinCos(endAngle, &stopPoint.fX);
Brian Osmanfd773fb2017-05-17 11:43:11 -0400991
992 // Adjust the start and end points based on the view matrix (to handle rotated arcs)
993 startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY);
994 stopPoint = viewMatrix.mapVector(stopPoint.fX, stopPoint.fY);
995 startPoint.normalize();
996 stopPoint.normalize();
997
998 // If the matrix included scale (on one axis) we need to swap our start and end points
999 if ((viewMatrix.getScaleX() < 0) != (viewMatrix.getScaleY() < 0)) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04001000 using std::swap;
1001 swap(startPoint, stopPoint);
Brian Osmanfd773fb2017-05-17 11:43:11 -04001002 }
1003
Brian Salomon45c92202018-04-10 10:53:58 -04001004 fRoundCaps = style.strokeRec().getWidth() > 0 &&
1005 style.strokeRec().getCap() == SkPaint::kRound_Cap;
1006 SkPoint roundCaps[2];
1007 if (fRoundCaps) {
1008 // Compute the cap center points in the normalized space.
1009 SkScalar midRadius = (innerRadius + outerRadius) / (2 * outerRadius);
1010 roundCaps[0] = startPoint * midRadius;
1011 roundCaps[1] = stopPoint * midRadius;
1012 } else {
1013 roundCaps[0] = kUnusedRoundCaps[0];
1014 roundCaps[1] = kUnusedRoundCaps[1];
1015 }
1016
bsalomon4f3a0ca2016-08-22 13:14:26 -07001017 // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against
Brian Salomon45c92202018-04-10 10:53:58 -04001018 // radial lines. We treat round caps the same way, but tack coverage of circles at the
1019 // center of the butts.
1020 // However, in both cases we have to be careful about the half-circle.
bsalomon4f3a0ca2016-08-22 13:14:26 -07001021 // case. In that case the two radial lines are equal and so that edge gets clipped
Brian Salomon45c92202018-04-10 10:53:58 -04001022 // twice. Since the shared edge goes through the center we fall back on the !useCenter
bsalomon4f3a0ca2016-08-22 13:14:26 -07001023 // case.
Brian Salomon45c92202018-04-10 10:53:58 -04001024 auto absSweep = SkScalarAbs(arcParams->fSweepAngleRadians);
1025 bool useCenter = (arcParams->fUseCenter || isStrokeOnly) &&
1026 !SkScalarNearlyEqual(absSweep, SK_ScalarPI);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001027 if (useCenter) {
1028 SkVector norm0 = {startPoint.fY, -startPoint.fX};
1029 SkVector norm1 = {stopPoint.fY, -stopPoint.fX};
1030 if (arcParams->fSweepAngleRadians > 0) {
1031 norm0.negate();
1032 } else {
1033 norm1.negate();
1034 }
Brian Salomon05441c42017-05-15 16:45:49 -04001035 fClipPlane = true;
Brian Salomon45c92202018-04-10 10:53:58 -04001036 if (absSweep > SK_ScalarPI) {
Brian Salomon05441c42017-05-15 16:45:49 -04001037 fCircles.emplace_back(Circle{
bsalomon4f3a0ca2016-08-22 13:14:26 -07001038 color,
1039 innerRadius,
1040 outerRadius,
1041 {norm0.fX, norm0.fY, 0.5f},
1042 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1043 {norm1.fX, norm1.fY, 0.5f},
Brian Salomon45c92202018-04-10 10:53:58 -04001044 {roundCaps[0], roundCaps[1]},
jvanverth6ca48822016-10-07 06:57:32 -07001045 devBounds,
Brian Salomon289e3d82016-12-14 15:52:56 -05001046 stroked});
Brian Salomon05441c42017-05-15 16:45:49 -04001047 fClipPlaneIsect = false;
1048 fClipPlaneUnion = true;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001049 } else {
Brian Salomon05441c42017-05-15 16:45:49 -04001050 fCircles.emplace_back(Circle{
bsalomon4f3a0ca2016-08-22 13:14:26 -07001051 color,
1052 innerRadius,
1053 outerRadius,
1054 {norm0.fX, norm0.fY, 0.5f},
1055 {norm1.fX, norm1.fY, 0.5f},
1056 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
Brian Salomon45c92202018-04-10 10:53:58 -04001057 {roundCaps[0], roundCaps[1]},
jvanverth6ca48822016-10-07 06:57:32 -07001058 devBounds,
Brian Salomon289e3d82016-12-14 15:52:56 -05001059 stroked});
Brian Salomon05441c42017-05-15 16:45:49 -04001060 fClipPlaneIsect = true;
1061 fClipPlaneUnion = false;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001062 }
1063 } else {
1064 // We clip to a secant of the original circle.
1065 startPoint.scale(radius);
1066 stopPoint.scale(radius);
1067 SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX};
1068 norm.normalize();
1069 if (arcParams->fSweepAngleRadians > 0) {
1070 norm.negate();
1071 }
1072 SkScalar d = -norm.dot(startPoint) + 0.5f;
1073
Brian Salomon05441c42017-05-15 16:45:49 -04001074 fCircles.emplace_back(
1075 Circle{color,
1076 innerRadius,
1077 outerRadius,
1078 {norm.fX, norm.fY, d},
1079 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1080 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
Brian Salomon45c92202018-04-10 10:53:58 -04001081 {roundCaps[0], roundCaps[1]},
Brian Salomon05441c42017-05-15 16:45:49 -04001082 devBounds,
1083 stroked});
1084 fClipPlane = true;
1085 fClipPlaneIsect = false;
1086 fClipPlaneUnion = false;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001087 }
1088 } else {
Brian Salomon05441c42017-05-15 16:45:49 -04001089 fCircles.emplace_back(
1090 Circle{color,
1091 innerRadius,
1092 outerRadius,
1093 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1094 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
1095 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
Brian Salomon45c92202018-04-10 10:53:58 -04001096 {kUnusedRoundCaps[0], kUnusedRoundCaps[1]},
Brian Salomon05441c42017-05-15 16:45:49 -04001097 devBounds,
1098 stroked});
1099 fClipPlane = false;
1100 fClipPlaneIsect = false;
1101 fClipPlaneUnion = false;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001102 }
bsalomon88cf17d2016-07-08 06:40:56 -07001103 // Use the original radius and stroke radius for the bounds so that it does not include the
1104 // AA bloat.
1105 radius += halfWidth;
Brian Salomon05441c42017-05-15 16:45:49 -04001106 this->setBounds(
Brian Salomon289e3d82016-12-14 15:52:56 -05001107 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
1108 HasAABloat::kYes, IsZeroArea::kNo);
Brian Salomon05441c42017-05-15 16:45:49 -04001109 fVertCount = circle_type_to_vert_count(stroked);
1110 fIndexCount = circle_type_to_index_count(stroked);
1111 fAllFill = !stroked;
bsalomoncdaa97b2016-03-08 08:30:14 -08001112 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07001113
Brian Salomon289e3d82016-12-14 15:52:56 -05001114 const char* name() const override { return "CircleOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08001115
Robert Phillipsf1748f52017-09-14 14:11:24 -04001116 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001117 fHelper.visitProxies(func);
1118 }
1119
robertphillipse004bfc2015-11-16 09:06:59 -08001120 SkString dumpInfo() const override {
1121 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04001122 for (int i = 0; i < fCircles.count(); ++i) {
Brian Salomon289e3d82016-12-14 15:52:56 -05001123 string.appendf(
1124 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1125 "InnerRad: %.2f, OuterRad: %.2f\n",
Brian Salomon05441c42017-05-15 16:45:49 -04001126 fCircles[i].fColor, fCircles[i].fDevBounds.fLeft, fCircles[i].fDevBounds.fTop,
1127 fCircles[i].fDevBounds.fRight, fCircles[i].fDevBounds.fBottom,
1128 fCircles[i].fInnerRadius, fCircles[i].fOuterRadius);
robertphillipse004bfc2015-11-16 09:06:59 -08001129 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04001130 string += fHelper.dumpInfo();
1131 string += INHERITED::dumpInfo();
robertphillipse004bfc2015-11-16 09:06:59 -08001132 return string;
1133 }
1134
Brian Osman9a725dd2017-09-20 09:53:22 -04001135 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
1136 GrPixelConfigIsClamped dstIsClamped) override {
Brian Salomon05441c42017-05-15 16:45:49 -04001137 GrColor* color = &fCircles.front().fColor;
Brian Osman9a725dd2017-09-20 09:53:22 -04001138 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
1139 GrProcessorAnalysisCoverage::kSingleChannel, color);
Brian Salomon05441c42017-05-15 16:45:49 -04001140 }
1141
1142 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1143
bsalomone46f9fe2015-08-18 06:05:14 -07001144private:
Brian Salomon91326c32017-08-09 16:02:19 -04001145 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08001146 SkMatrix localMatrix;
1147 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001148 return;
1149 }
1150
1151 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05001152 sk_sp<GrGeometryProcessor> gp(new CircleGeometryProcessor(
Brian Salomon45c92202018-04-10 10:53:58 -04001153 !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, fRoundCaps, localMatrix));
bsalomon4f3a0ca2016-08-22 13:14:26 -07001154
1155 struct CircleVertex {
Brian Salomon289e3d82016-12-14 15:52:56 -05001156 SkPoint fPos;
1157 GrColor fColor;
1158 SkPoint fOffset;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001159 SkScalar fOuterRadius;
1160 SkScalar fInnerRadius;
1161 // These planes may or may not be present in the vertex buffer.
1162 SkScalar fHalfPlanes[3][3];
1163 };
joshualitt76e7fb62015-02-11 08:52:27 -08001164
Brian Salomon45c92202018-04-10 10:53:58 -04001165 int numPlanes = (int)fClipPlane + fClipPlaneIsect + fClipPlaneUnion;
1166 auto vertexCapCenters = [numPlanes](CircleVertex* v) {
1167 return (void*)(v->fHalfPlanes + numPlanes);
1168 };
Brian Salomon92be2f72018-06-19 14:33:47 -04001169 size_t vertexStride = sizeof(CircleVertex) - (fClipPlane ? 0 : 3 * sizeof(SkScalar)) -
1170 (fClipPlaneIsect ? 0 : 3 * sizeof(SkScalar)) -
1171 (fClipPlaneUnion ? 0 : 3 * sizeof(SkScalar)) +
1172 (fRoundCaps ? 2 * sizeof(SkPoint) : 0);
1173 SkASSERT(vertexStride == gp->debugOnly_vertexStride());
jvanverth6ca48822016-10-07 06:57:32 -07001174
1175 const GrBuffer* vertexBuffer;
1176 int firstVertex;
Brian Salomon289e3d82016-12-14 15:52:56 -05001177 char* vertices = (char*)target->makeVertexSpace(vertexStride, fVertCount, &vertexBuffer,
1178 &firstVertex);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001179 if (!vertices) {
jvanverth6ca48822016-10-07 06:57:32 -07001180 SkDebugf("Could not allocate vertices\n");
joshualitt4b31de82015-03-05 14:33:41 -08001181 return;
1182 }
1183
jvanverth6ca48822016-10-07 06:57:32 -07001184 const GrBuffer* indexBuffer = nullptr;
1185 int firstIndex = 0;
1186 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1187 if (!indices) {
1188 SkDebugf("Could not allocate indices\n");
1189 return;
1190 }
1191
1192 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04001193 for (const auto& circle : fCircles) {
1194 SkScalar innerRadius = circle.fInnerRadius;
1195 SkScalar outerRadius = circle.fOuterRadius;
1196 GrColor color = circle.fColor;
1197 const SkRect& bounds = circle.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08001198
Brian Salomon289e3d82016-12-14 15:52:56 -05001199 CircleVertex* v0 = reinterpret_cast<CircleVertex*>(vertices + 0 * vertexStride);
1200 CircleVertex* v1 = reinterpret_cast<CircleVertex*>(vertices + 1 * vertexStride);
1201 CircleVertex* v2 = reinterpret_cast<CircleVertex*>(vertices + 2 * vertexStride);
1202 CircleVertex* v3 = reinterpret_cast<CircleVertex*>(vertices + 3 * vertexStride);
1203 CircleVertex* v4 = reinterpret_cast<CircleVertex*>(vertices + 4 * vertexStride);
1204 CircleVertex* v5 = reinterpret_cast<CircleVertex*>(vertices + 5 * vertexStride);
1205 CircleVertex* v6 = reinterpret_cast<CircleVertex*>(vertices + 6 * vertexStride);
1206 CircleVertex* v7 = reinterpret_cast<CircleVertex*>(vertices + 7 * vertexStride);
joshualitt76e7fb62015-02-11 08:52:27 -08001207
1208 // The inner radius in the vertex data must be specified in normalized space.
1209 innerRadius = innerRadius / outerRadius;
jvanverth6ca48822016-10-07 06:57:32 -07001210
1211 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
Brian Salomon289e3d82016-12-14 15:52:56 -05001212 SkScalar halfWidth = 0.5f * bounds.width();
jvanverth6ca48822016-10-07 06:57:32 -07001213 SkScalar octOffset = 0.41421356237f; // sqrt(2) - 1
Herb Derby60c05f92016-12-13 15:18:55 -05001214
Brian Salomon289e3d82016-12-14 15:52:56 -05001215 v0->fPos = center + SkPoint::Make(-octOffset * halfWidth, -halfWidth);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001216 v0->fColor = color;
jvanverth6ca48822016-10-07 06:57:32 -07001217 v0->fOffset = SkPoint::Make(-octOffset, -1);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001218 v0->fOuterRadius = outerRadius;
1219 v0->fInnerRadius = innerRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001220
Brian Salomon289e3d82016-12-14 15:52:56 -05001221 v1->fPos = center + SkPoint::Make(octOffset * halfWidth, -halfWidth);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001222 v1->fColor = color;
jvanverth6ca48822016-10-07 06:57:32 -07001223 v1->fOffset = SkPoint::Make(octOffset, -1);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001224 v1->fOuterRadius = outerRadius;
1225 v1->fInnerRadius = innerRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001226
Brian Salomon289e3d82016-12-14 15:52:56 -05001227 v2->fPos = center + SkPoint::Make(halfWidth, -octOffset * halfWidth);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001228 v2->fColor = color;
jvanverth6ca48822016-10-07 06:57:32 -07001229 v2->fOffset = SkPoint::Make(1, -octOffset);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001230 v2->fOuterRadius = outerRadius;
1231 v2->fInnerRadius = innerRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001232
Brian Salomon289e3d82016-12-14 15:52:56 -05001233 v3->fPos = center + SkPoint::Make(halfWidth, octOffset * halfWidth);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001234 v3->fColor = color;
jvanverth6ca48822016-10-07 06:57:32 -07001235 v3->fOffset = SkPoint::Make(1, octOffset);
bsalomon4f3a0ca2016-08-22 13:14:26 -07001236 v3->fOuterRadius = outerRadius;
1237 v3->fInnerRadius = innerRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001238
Brian Salomon289e3d82016-12-14 15:52:56 -05001239 v4->fPos = center + SkPoint::Make(octOffset * halfWidth, halfWidth);
jvanverth6ca48822016-10-07 06:57:32 -07001240 v4->fColor = color;
1241 v4->fOffset = SkPoint::Make(octOffset, 1);
1242 v4->fOuterRadius = outerRadius;
1243 v4->fInnerRadius = innerRadius;
1244
Brian Salomon289e3d82016-12-14 15:52:56 -05001245 v5->fPos = center + SkPoint::Make(-octOffset * halfWidth, halfWidth);
jvanverth6ca48822016-10-07 06:57:32 -07001246 v5->fColor = color;
1247 v5->fOffset = SkPoint::Make(-octOffset, 1);
1248 v5->fOuterRadius = outerRadius;
1249 v5->fInnerRadius = innerRadius;
1250
Brian Salomon289e3d82016-12-14 15:52:56 -05001251 v6->fPos = center + SkPoint::Make(-halfWidth, octOffset * halfWidth);
jvanverth6ca48822016-10-07 06:57:32 -07001252 v6->fColor = color;
1253 v6->fOffset = SkPoint::Make(-1, octOffset);
1254 v6->fOuterRadius = outerRadius;
1255 v6->fInnerRadius = innerRadius;
1256
Brian Salomon289e3d82016-12-14 15:52:56 -05001257 v7->fPos = center + SkPoint::Make(-halfWidth, -octOffset * halfWidth);
jvanverth6ca48822016-10-07 06:57:32 -07001258 v7->fColor = color;
1259 v7->fOffset = SkPoint::Make(-1, -octOffset);
1260 v7->fOuterRadius = outerRadius;
1261 v7->fInnerRadius = innerRadius;
1262
bsalomon4f3a0ca2016-08-22 13:14:26 -07001263 if (fClipPlane) {
Brian Salomon05441c42017-05-15 16:45:49 -04001264 memcpy(v0->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1265 memcpy(v1->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1266 memcpy(v2->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1267 memcpy(v3->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1268 memcpy(v4->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1269 memcpy(v5->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1270 memcpy(v6->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1271 memcpy(v7->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
bsalomon4f3a0ca2016-08-22 13:14:26 -07001272 }
1273 int unionIdx = 1;
1274 if (fClipPlaneIsect) {
Brian Salomon05441c42017-05-15 16:45:49 -04001275 memcpy(v0->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1276 memcpy(v1->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1277 memcpy(v2->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1278 memcpy(v3->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1279 memcpy(v4->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1280 memcpy(v5->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1281 memcpy(v6->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1282 memcpy(v7->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
bsalomon4f3a0ca2016-08-22 13:14:26 -07001283 unionIdx = 2;
1284 }
1285 if (fClipPlaneUnion) {
Brian Salomon05441c42017-05-15 16:45:49 -04001286 memcpy(v0->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1287 memcpy(v1->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1288 memcpy(v2->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1289 memcpy(v3->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1290 memcpy(v4->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1291 memcpy(v5->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1292 memcpy(v6->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1293 memcpy(v7->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
bsalomon4f3a0ca2016-08-22 13:14:26 -07001294 }
Brian Salomon45c92202018-04-10 10:53:58 -04001295 if (fRoundCaps) {
1296 memcpy(vertexCapCenters(v0), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1297 memcpy(vertexCapCenters(v1), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1298 memcpy(vertexCapCenters(v2), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1299 memcpy(vertexCapCenters(v3), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1300 memcpy(vertexCapCenters(v4), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1301 memcpy(vertexCapCenters(v5), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1302 memcpy(vertexCapCenters(v6), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1303 memcpy(vertexCapCenters(v7), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1304 }
jvanverth6ca48822016-10-07 06:57:32 -07001305
Brian Salomon05441c42017-05-15 16:45:49 -04001306 if (circle.fStroked) {
jvanverth6ca48822016-10-07 06:57:32 -07001307 // compute the inner ring
1308 CircleVertex* v0 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
1309 CircleVertex* v1 = reinterpret_cast<CircleVertex*>(vertices + 9 * vertexStride);
1310 CircleVertex* v2 = reinterpret_cast<CircleVertex*>(vertices + 10 * vertexStride);
1311 CircleVertex* v3 = reinterpret_cast<CircleVertex*>(vertices + 11 * vertexStride);
1312 CircleVertex* v4 = reinterpret_cast<CircleVertex*>(vertices + 12 * vertexStride);
1313 CircleVertex* v5 = reinterpret_cast<CircleVertex*>(vertices + 13 * vertexStride);
1314 CircleVertex* v6 = reinterpret_cast<CircleVertex*>(vertices + 14 * vertexStride);
1315 CircleVertex* v7 = reinterpret_cast<CircleVertex*>(vertices + 15 * vertexStride);
1316
1317 // cosine and sine of pi/8
1318 SkScalar c = 0.923579533f;
1319 SkScalar s = 0.382683432f;
Brian Salomon05441c42017-05-15 16:45:49 -04001320 SkScalar r = circle.fInnerRadius;
jvanverth6ca48822016-10-07 06:57:32 -07001321
Brian Salomon289e3d82016-12-14 15:52:56 -05001322 v0->fPos = center + SkPoint::Make(-s * r, -c * r);
jvanverth6ca48822016-10-07 06:57:32 -07001323 v0->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001324 v0->fOffset = SkPoint::Make(-s * innerRadius, -c * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001325 v0->fOuterRadius = outerRadius;
1326 v0->fInnerRadius = innerRadius;
1327
Brian Salomon289e3d82016-12-14 15:52:56 -05001328 v1->fPos = center + SkPoint::Make(s * r, -c * r);
jvanverth6ca48822016-10-07 06:57:32 -07001329 v1->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001330 v1->fOffset = SkPoint::Make(s * innerRadius, -c * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001331 v1->fOuterRadius = outerRadius;
1332 v1->fInnerRadius = innerRadius;
1333
Brian Salomon289e3d82016-12-14 15:52:56 -05001334 v2->fPos = center + SkPoint::Make(c * r, -s * r);
jvanverth6ca48822016-10-07 06:57:32 -07001335 v2->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001336 v2->fOffset = SkPoint::Make(c * innerRadius, -s * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001337 v2->fOuterRadius = outerRadius;
1338 v2->fInnerRadius = innerRadius;
1339
Brian Salomon289e3d82016-12-14 15:52:56 -05001340 v3->fPos = center + SkPoint::Make(c * r, s * r);
jvanverth6ca48822016-10-07 06:57:32 -07001341 v3->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001342 v3->fOffset = SkPoint::Make(c * innerRadius, s * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001343 v3->fOuterRadius = outerRadius;
1344 v3->fInnerRadius = innerRadius;
1345
Brian Salomon289e3d82016-12-14 15:52:56 -05001346 v4->fPos = center + SkPoint::Make(s * r, c * r);
jvanverth6ca48822016-10-07 06:57:32 -07001347 v4->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001348 v4->fOffset = SkPoint::Make(s * innerRadius, c * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001349 v4->fOuterRadius = outerRadius;
1350 v4->fInnerRadius = innerRadius;
1351
Brian Salomon289e3d82016-12-14 15:52:56 -05001352 v5->fPos = center + SkPoint::Make(-s * r, c * r);
jvanverth6ca48822016-10-07 06:57:32 -07001353 v5->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001354 v5->fOffset = SkPoint::Make(-s * innerRadius, c * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001355 v5->fOuterRadius = outerRadius;
1356 v5->fInnerRadius = innerRadius;
1357
Brian Salomon289e3d82016-12-14 15:52:56 -05001358 v6->fPos = center + SkPoint::Make(-c * r, s * r);
jvanverth6ca48822016-10-07 06:57:32 -07001359 v6->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001360 v6->fOffset = SkPoint::Make(-c * innerRadius, s * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001361 v6->fOuterRadius = outerRadius;
1362 v6->fInnerRadius = innerRadius;
1363
Brian Salomon289e3d82016-12-14 15:52:56 -05001364 v7->fPos = center + SkPoint::Make(-c * r, -s * r);
jvanverth6ca48822016-10-07 06:57:32 -07001365 v7->fColor = color;
Brian Salomon289e3d82016-12-14 15:52:56 -05001366 v7->fOffset = SkPoint::Make(-c * innerRadius, -s * innerRadius);
jvanverth6ca48822016-10-07 06:57:32 -07001367 v7->fOuterRadius = outerRadius;
1368 v7->fInnerRadius = innerRadius;
1369
1370 if (fClipPlane) {
Brian Salomon05441c42017-05-15 16:45:49 -04001371 memcpy(v0->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1372 memcpy(v1->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1373 memcpy(v2->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1374 memcpy(v3->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1375 memcpy(v4->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1376 memcpy(v5->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1377 memcpy(v6->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
1378 memcpy(v7->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
jvanverth6ca48822016-10-07 06:57:32 -07001379 }
1380 int unionIdx = 1;
1381 if (fClipPlaneIsect) {
Brian Salomon05441c42017-05-15 16:45:49 -04001382 memcpy(v0->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1383 memcpy(v1->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1384 memcpy(v2->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1385 memcpy(v3->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1386 memcpy(v4->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1387 memcpy(v5->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1388 memcpy(v6->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
1389 memcpy(v7->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
jvanverth6ca48822016-10-07 06:57:32 -07001390 unionIdx = 2;
1391 }
1392 if (fClipPlaneUnion) {
Brian Salomon05441c42017-05-15 16:45:49 -04001393 memcpy(v0->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1394 memcpy(v1->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1395 memcpy(v2->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1396 memcpy(v3->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1397 memcpy(v4->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1398 memcpy(v5->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1399 memcpy(v6->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
1400 memcpy(v7->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
jvanverth6ca48822016-10-07 06:57:32 -07001401 }
Brian Salomon45c92202018-04-10 10:53:58 -04001402 if (fRoundCaps) {
1403 memcpy(vertexCapCenters(v0), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1404 memcpy(vertexCapCenters(v1), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1405 memcpy(vertexCapCenters(v2), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1406 memcpy(vertexCapCenters(v3), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1407 memcpy(vertexCapCenters(v4), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1408 memcpy(vertexCapCenters(v5), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1409 memcpy(vertexCapCenters(v6), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1410 memcpy(vertexCapCenters(v7), circle.fRoundCapCenters, 2 * sizeof(SkPoint));
1411 }
jvanverth6ca48822016-10-07 06:57:32 -07001412 } else {
1413 // filled
1414 CircleVertex* v8 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
1415 v8->fPos = center;
1416 v8->fColor = color;
1417 v8->fOffset = SkPoint::Make(0, 0);
1418 v8->fOuterRadius = outerRadius;
1419 v8->fInnerRadius = innerRadius;
1420 if (fClipPlane) {
Brian Salomon05441c42017-05-15 16:45:49 -04001421 memcpy(v8->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar));
jvanverth6ca48822016-10-07 06:57:32 -07001422 }
1423 int unionIdx = 1;
1424 if (fClipPlaneIsect) {
Brian Salomon05441c42017-05-15 16:45:49 -04001425 memcpy(v8->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar));
jvanverth6ca48822016-10-07 06:57:32 -07001426 unionIdx = 2;
1427 }
1428 if (fClipPlaneUnion) {
Brian Salomon05441c42017-05-15 16:45:49 -04001429 memcpy(v8->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar));
jvanverth6ca48822016-10-07 06:57:32 -07001430 }
Brian Salomon45c92202018-04-10 10:53:58 -04001431 SkASSERT(!fRoundCaps);
jvanverth6ca48822016-10-07 06:57:32 -07001432 }
1433
Brian Salomon05441c42017-05-15 16:45:49 -04001434 const uint16_t* primIndices = circle_type_to_indices(circle.fStroked);
1435 const int primIndexCount = circle_type_to_index_count(circle.fStroked);
jvanverth6ca48822016-10-07 06:57:32 -07001436 for (int i = 0; i < primIndexCount; ++i) {
1437 *indices++ = primIndices[i] + currStartVertex;
1438 }
1439
Brian Salomon05441c42017-05-15 16:45:49 -04001440 currStartVertex += circle_type_to_vert_count(circle.fStroked);
1441 vertices += circle_type_to_vert_count(circle.fStroked) * vertexStride;
joshualitt76e7fb62015-02-11 08:52:27 -08001442 }
jvanverth6ca48822016-10-07 06:57:32 -07001443
Chris Dalton3809bab2017-06-13 10:55:06 -06001444 GrMesh mesh(GrPrimitiveType::kTriangles);
Brian Salomon802cb312018-06-08 18:05:20 -04001445 mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
1446 GrPrimitiveRestart::kNo);
Chris Dalton114a3c02017-05-26 15:17:19 -06001447 mesh.setVertexData(vertexBuffer, firstVertex);
Brian Salomon05441c42017-05-15 16:45:49 -04001448 target->draw(gp.get(), fHelper.makePipeline(target), mesh);
joshualitt76e7fb62015-02-11 08:52:27 -08001449 }
1450
Brian Salomon25a88092016-12-01 09:36:50 -05001451 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05001452 CircleOp* that = t->cast<CircleOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05001453
1454 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05001455 if (fVertCount + that->fVertCount > 65536) {
Jim Van Verth8cefe402017-02-09 11:36:37 -05001456 return false;
1457 }
1458
Brian Salomon05441c42017-05-15 16:45:49 -04001459 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
joshualitt8cab9a72015-07-16 09:13:50 -07001460 return false;
1461 }
1462
Brian Salomon05441c42017-05-15 16:45:49 -04001463 if (fHelper.usesLocalCoords() &&
1464 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001465 return false;
1466 }
1467
Brian Salomon289e3d82016-12-14 15:52:56 -05001468 // Because we've set up the ops that don't use the planes with noop values
1469 // we can just accumulate used planes by later ops.
bsalomon4f3a0ca2016-08-22 13:14:26 -07001470 fClipPlane |= that->fClipPlane;
1471 fClipPlaneIsect |= that->fClipPlaneIsect;
1472 fClipPlaneUnion |= that->fClipPlaneUnion;
Brian Salomon45c92202018-04-10 10:53:58 -04001473 fRoundCaps |= that->fRoundCaps;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001474
Brian Salomon05441c42017-05-15 16:45:49 -04001475 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
bsalomon88cf17d2016-07-08 06:40:56 -07001476 this->joinBounds(*that);
jvanverth6ca48822016-10-07 06:57:32 -07001477 fVertCount += that->fVertCount;
1478 fIndexCount += that->fIndexCount;
1479 fAllFill = fAllFill && that->fAllFill;
joshualitt76e7fb62015-02-11 08:52:27 -08001480 return true;
1481 }
1482
Brian Salomon05441c42017-05-15 16:45:49 -04001483 struct Circle {
Brian Salomon289e3d82016-12-14 15:52:56 -05001484 GrColor fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001485 SkScalar fInnerRadius;
1486 SkScalar fOuterRadius;
bsalomon4f3a0ca2016-08-22 13:14:26 -07001487 SkScalar fClipPlane[3];
1488 SkScalar fIsectPlane[3];
1489 SkScalar fUnionPlane[3];
Brian Salomon45c92202018-04-10 10:53:58 -04001490 SkPoint fRoundCapCenters[2];
Brian Salomon289e3d82016-12-14 15:52:56 -05001491 SkRect fDevBounds;
1492 bool fStroked;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001493 };
1494
Brian Salomon289e3d82016-12-14 15:52:56 -05001495 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04001496 Helper fHelper;
1497 SkSTArray<1, Circle, true> fCircles;
Brian Salomon289e3d82016-12-14 15:52:56 -05001498 int fVertCount;
1499 int fIndexCount;
1500 bool fAllFill;
1501 bool fClipPlane;
1502 bool fClipPlaneIsect;
1503 bool fClipPlaneUnion;
Brian Salomon45c92202018-04-10 10:53:58 -04001504 bool fRoundCaps;
reed1b55a962015-09-17 20:16:13 -07001505
Brian Salomon05441c42017-05-15 16:45:49 -04001506 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08001507};
1508
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001509class ButtCapDashedCircleOp final : public GrMeshDrawOp {
1510private:
1511 using Helper = GrSimpleMeshDrawOpHelper;
1512
1513public:
1514 DEFINE_OP_CLASS_ID
1515
Robert Phillips7c525e62018-06-12 10:11:12 -04001516 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
1517 GrPaint&& paint,
1518 const SkMatrix& viewMatrix,
1519 SkPoint center,
1520 SkScalar radius,
1521 SkScalar strokeWidth,
1522 SkScalar startAngle,
1523 SkScalar onAngle,
1524 SkScalar offAngle,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001525 SkScalar phaseAngle) {
1526 SkASSERT(circle_stays_circle(viewMatrix));
1527 SkASSERT(strokeWidth < 2 * radius);
Robert Phillips7c525e62018-06-12 10:11:12 -04001528 return Helper::FactoryHelper<ButtCapDashedCircleOp>(context, std::move(paint), viewMatrix,
1529 center, radius, strokeWidth, startAngle,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001530 onAngle, offAngle, phaseAngle);
1531 }
1532
1533 ButtCapDashedCircleOp(const Helper::MakeArgs& helperArgs, GrColor color,
1534 const SkMatrix& viewMatrix, SkPoint center, SkScalar radius,
1535 SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle,
1536 SkScalar offAngle, SkScalar phaseAngle)
1537 : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
1538 SkASSERT(circle_stays_circle(viewMatrix));
1539 viewMatrix.mapPoints(&center, 1);
1540 radius = viewMatrix.mapRadius(radius);
1541 strokeWidth = viewMatrix.mapRadius(strokeWidth);
1542
1543 // Determine the angle where the circle starts in device space and whether its orientation
1544 // has been reversed.
1545 SkVector start;
1546 bool reflection;
1547 if (!startAngle) {
1548 start = {1, 0};
1549 } else {
1550 start.fY = SkScalarSinCos(startAngle, &start.fX);
1551 }
1552 viewMatrix.mapVectors(&start, 1);
1553 startAngle = SkScalarATan2(start.fY, start.fX);
1554 reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() -
1555 viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0;
1556
1557 auto totalAngle = onAngle + offAngle;
1558 phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2;
1559
1560 SkScalar halfWidth = 0;
1561 if (SkScalarNearlyZero(strokeWidth)) {
1562 halfWidth = SK_ScalarHalf;
1563 } else {
1564 halfWidth = SkScalarHalf(strokeWidth);
1565 }
1566
1567 SkScalar outerRadius = radius + halfWidth;
1568 SkScalar innerRadius = radius - halfWidth;
1569
1570 // The radii are outset for two reasons. First, it allows the shader to simply perform
1571 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
1572 // Second, the outer radius is used to compute the verts of the bounding box that is
1573 // rendered and the outset ensures the box will cover all partially covered by the circle.
1574 outerRadius += SK_ScalarHalf;
1575 innerRadius -= SK_ScalarHalf;
1576 fViewMatrixIfUsingLocalCoords = viewMatrix;
1577
1578 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
1579 center.fX + outerRadius, center.fY + outerRadius);
1580
1581 // We store whether there is a reflection as a negative total angle.
1582 if (reflection) {
1583 totalAngle = -totalAngle;
1584 }
1585 fCircles.push_back(Circle{
1586 color,
1587 outerRadius,
1588 innerRadius,
1589 onAngle,
1590 totalAngle,
1591 startAngle,
1592 phaseAngle,
1593 devBounds
1594 });
1595 // Use the original radius and stroke radius for the bounds so that it does not include the
1596 // AA bloat.
1597 radius += halfWidth;
1598 this->setBounds(
1599 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
1600 HasAABloat::kYes, IsZeroArea::kNo);
1601 fVertCount = circle_type_to_vert_count(true);
1602 fIndexCount = circle_type_to_index_count(true);
1603 }
1604
1605 const char* name() const override { return "ButtCappedDashedCircleOp"; }
1606
1607 void visitProxies(const VisitProxyFunc& func) const override { fHelper.visitProxies(func); }
1608
1609 SkString dumpInfo() const override {
1610 SkString string;
1611 for (int i = 0; i < fCircles.count(); ++i) {
1612 string.appendf(
1613 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
1614 "InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, "
1615 "Phase: %.2f\n",
1616 fCircles[i].fColor, fCircles[i].fDevBounds.fLeft, fCircles[i].fDevBounds.fTop,
1617 fCircles[i].fDevBounds.fRight, fCircles[i].fDevBounds.fBottom,
1618 fCircles[i].fInnerRadius, fCircles[i].fOuterRadius, fCircles[i].fOnAngle,
1619 fCircles[i].fTotalAngle, fCircles[i].fPhaseAngle);
1620 }
1621 string += fHelper.dumpInfo();
1622 string += INHERITED::dumpInfo();
1623 return string;
1624 }
1625
1626 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
1627 GrPixelConfigIsClamped dstIsClamped) override {
1628 GrColor* color = &fCircles.front().fColor;
1629 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
1630 GrProcessorAnalysisCoverage::kSingleChannel, color);
1631 }
1632
1633 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1634
1635private:
1636 void onPrepareDraws(Target* target) override {
1637 SkMatrix localMatrix;
1638 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
1639 return;
1640 }
1641
1642 // Setup geometry processor
1643 sk_sp<GrGeometryProcessor> gp(new ButtCapDashedCircleGeometryProcessor(localMatrix));
1644
1645 struct CircleVertex {
1646 SkPoint fPos;
1647 GrColor fColor;
1648 SkPoint fOffset;
1649 SkScalar fOuterRadius;
1650 SkScalar fInnerRadius;
1651 SkScalar fOnAngle;
1652 SkScalar fTotalAngle;
1653 SkScalar fStartAngle;
1654 SkScalar fPhaseAngle;
1655 };
1656
Brian Salomon92be2f72018-06-19 14:33:47 -04001657 static constexpr size_t kVertexStride = sizeof(CircleVertex);
1658 SkASSERT(kVertexStride == gp->debugOnly_vertexStride());
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001659
1660 const GrBuffer* vertexBuffer;
1661 int firstVertex;
Brian Salomon92be2f72018-06-19 14:33:47 -04001662 char* vertices = (char*)target->makeVertexSpace(kVertexStride, fVertCount, &vertexBuffer,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001663 &firstVertex);
1664 if (!vertices) {
1665 SkDebugf("Could not allocate vertices\n");
1666 return;
1667 }
1668
1669 const GrBuffer* indexBuffer = nullptr;
1670 int firstIndex = 0;
1671 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
1672 if (!indices) {
1673 SkDebugf("Could not allocate indices\n");
1674 return;
1675 }
1676
1677 int currStartVertex = 0;
1678 for (const auto& circle : fCircles) {
1679 // The inner radius in the vertex data must be specified in normalized space so that
1680 // length() can be called with smaller values to avoid precision issues with half
1681 // floats.
1682 auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius;
1683 const SkRect& bounds = circle.fDevBounds;
1684 bool reflect = false;
1685 SkScalar totalAngle = circle.fTotalAngle;
1686 if (totalAngle < 0) {
1687 reflect = true;
1688 totalAngle = -totalAngle;
1689 }
1690
1691 // The bounding geometry for the circle is composed of an outer bounding octagon and
1692 // an inner bounded octagon.
1693
1694 // Initializes the attributes that are the same at each vertex. Also applies reflection.
1695 auto init_const_attrs_and_reflect = [&](CircleVertex* v) {
1696 v->fColor = circle.fColor;
1697 v->fOuterRadius = circle.fOuterRadius;
1698 v->fInnerRadius = normInnerRadius;
1699 v->fOnAngle = circle.fOnAngle;
1700 v->fTotalAngle = totalAngle;
1701 v->fStartAngle = circle.fStartAngle;
1702 v->fPhaseAngle = circle.fPhaseAngle;
1703 if (reflect) {
1704 v->fStartAngle = -v->fStartAngle;
1705 v->fOffset.fY = -v->fOffset.fY;
1706 }
1707 };
1708
1709 // Compute the vertices of the outer octagon.
1710 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
1711 SkScalar halfWidth = 0.5f * bounds.width();
1712 auto init_outer_vertex = [&](int idx, SkScalar x, SkScalar y) {
Brian Salomon92be2f72018-06-19 14:33:47 -04001713 CircleVertex* v = reinterpret_cast<CircleVertex*>(vertices + idx * kVertexStride);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001714 v->fPos = center + SkPoint{x * halfWidth, y * halfWidth};
1715 v->fOffset = {x, y};
1716 init_const_attrs_and_reflect(v);
1717 };
1718 static constexpr SkScalar kOctOffset = 0.41421356237f; // sqrt(2) - 1
1719 init_outer_vertex(0, -kOctOffset, -1);
1720 init_outer_vertex(1, kOctOffset, -1);
1721 init_outer_vertex(2, 1, -kOctOffset);
1722 init_outer_vertex(3, 1, kOctOffset);
1723 init_outer_vertex(4, kOctOffset, 1);
1724 init_outer_vertex(5, -kOctOffset, 1);
1725 init_outer_vertex(6, -1, kOctOffset);
1726 init_outer_vertex(7, -1, -kOctOffset);
1727
1728 // Compute the vertices of the inner octagon.
1729 auto init_inner_vertex = [&](int idx, SkScalar x, SkScalar y) {
1730 CircleVertex* v =
Brian Salomon92be2f72018-06-19 14:33:47 -04001731 reinterpret_cast<CircleVertex*>(vertices + (idx + 8) * kVertexStride);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001732 v->fPos = center + SkPoint{x * circle.fInnerRadius, y * circle.fInnerRadius};
1733 v->fOffset = {x * normInnerRadius, y * normInnerRadius};
1734 init_const_attrs_and_reflect(v);
1735 };
1736
1737 // cosine and sine of pi/8
1738 static constexpr SkScalar kCos = 0.923579533f;
1739 static constexpr SkScalar kSin = 0.382683432f;
1740
1741 init_inner_vertex(0, -kSin, -kCos);
1742 init_inner_vertex(1, kSin, -kCos);
1743 init_inner_vertex(2, kCos, -kSin);
1744 init_inner_vertex(3, kCos, kSin);
1745 init_inner_vertex(4, kSin, kCos);
1746 init_inner_vertex(5, -kSin, kCos);
1747 init_inner_vertex(6, -kCos, kSin);
1748 init_inner_vertex(7, -kCos, -kSin);
1749
1750 const uint16_t* primIndices = circle_type_to_indices(true);
1751 const int primIndexCount = circle_type_to_index_count(true);
1752 for (int i = 0; i < primIndexCount; ++i) {
1753 *indices++ = primIndices[i] + currStartVertex;
1754 }
1755
1756 currStartVertex += circle_type_to_vert_count(true);
Brian Salomon92be2f72018-06-19 14:33:47 -04001757 vertices += circle_type_to_vert_count(true) * kVertexStride;
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001758 }
1759
1760 GrMesh mesh(GrPrimitiveType::kTriangles);
Brian Salomon802cb312018-06-08 18:05:20 -04001761 mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
1762 GrPrimitiveRestart::kNo);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04001763 mesh.setVertexData(vertexBuffer, firstVertex);
1764 target->draw(gp.get(), fHelper.makePipeline(target), mesh);
1765 }
1766
1767 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
1768 ButtCapDashedCircleOp* that = t->cast<ButtCapDashedCircleOp>();
1769
1770 // can only represent 65535 unique vertices with 16-bit indices
1771 if (fVertCount + that->fVertCount > 65536) {
1772 return false;
1773 }
1774
1775 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
1776 return false;
1777 }
1778
1779 if (fHelper.usesLocalCoords() &&
1780 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
1781 return false;
1782 }
1783
1784 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
1785 this->joinBounds(*that);
1786 fVertCount += that->fVertCount;
1787 fIndexCount += that->fIndexCount;
1788 return true;
1789 }
1790
1791 struct Circle {
1792 GrColor fColor;
1793 SkScalar fOuterRadius;
1794 SkScalar fInnerRadius;
1795 SkScalar fOnAngle;
1796 SkScalar fTotalAngle;
1797 SkScalar fStartAngle;
1798 SkScalar fPhaseAngle;
1799 SkRect fDevBounds;
1800 };
1801
1802 SkMatrix fViewMatrixIfUsingLocalCoords;
1803 Helper fHelper;
1804 SkSTArray<1, Circle, true> fCircles;
1805 int fVertCount;
1806 int fIndexCount;
1807
1808 typedef GrMeshDrawOp INHERITED;
1809};
1810
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00001811///////////////////////////////////////////////////////////////////////////////
1812
Brian Salomon05441c42017-05-15 16:45:49 -04001813class EllipseOp : public GrMeshDrawOp {
1814private:
1815 using Helper = GrSimpleMeshDrawOpHelper;
1816
1817 struct DeviceSpaceParams {
1818 SkPoint fCenter;
1819 SkScalar fXRadius;
1820 SkScalar fYRadius;
1821 SkScalar fInnerXRadius;
1822 SkScalar fInnerYRadius;
1823 };
1824
joshualitt76e7fb62015-02-11 08:52:27 -08001825public:
Brian Salomon25a88092016-12-01 09:36:50 -05001826 DEFINE_OP_CLASS_ID
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001827
Robert Phillips7c525e62018-06-12 10:11:12 -04001828 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
1829 GrPaint&& paint,
1830 const SkMatrix& viewMatrix,
1831 const SkRect& ellipse,
1832 const SkStrokeRec& stroke) {
Brian Salomon05441c42017-05-15 16:45:49 -04001833 DeviceSpaceParams params;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001834 // do any matrix crunching before we reset the draw state for device coords
Brian Salomon05441c42017-05-15 16:45:49 -04001835 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
1836 viewMatrix.mapPoints(&params.fCenter, 1);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001837 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width());
1838 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height());
Brian Salomon05441c42017-05-15 16:45:49 -04001839 params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius +
1840 viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius);
1841 params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius +
1842 viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08001843
bsalomon4b4a7cc2016-07-08 04:42:54 -07001844 // do (potentially) anisotropic mapping of stroke
1845 SkVector scaledStroke;
1846 SkScalar strokeWidth = stroke.getWidth();
Brian Salomon289e3d82016-12-14 15:52:56 -05001847 scaledStroke.fX = SkScalarAbs(
1848 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
1849 scaledStroke.fY = SkScalarAbs(
1850 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
bsalomon4b4a7cc2016-07-08 04:42:54 -07001851
1852 SkStrokeRec::Style style = stroke.getStyle();
Brian Salomon289e3d82016-12-14 15:52:56 -05001853 bool isStrokeOnly =
1854 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001855 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
1856
Brian Salomon05441c42017-05-15 16:45:49 -04001857 params.fInnerXRadius = 0;
1858 params.fInnerYRadius = 0;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001859 if (hasStroke) {
1860 if (SkScalarNearlyZero(scaledStroke.length())) {
1861 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
1862 } else {
1863 scaledStroke.scale(SK_ScalarHalf);
1864 }
1865
1866 // we only handle thick strokes for near-circular ellipses
1867 if (scaledStroke.length() > SK_ScalarHalf &&
Brian Salomon05441c42017-05-15 16:45:49 -04001868 (0.5f * params.fXRadius > params.fYRadius ||
1869 0.5f * params.fYRadius > params.fXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07001870 return nullptr;
1871 }
1872
1873 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon05441c42017-05-15 16:45:49 -04001874 if (scaledStroke.fX * (params.fXRadius * params.fYRadius) <
1875 (scaledStroke.fY * scaledStroke.fY) * params.fXRadius ||
1876 scaledStroke.fY * (params.fXRadius * params.fXRadius) <
1877 (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07001878 return nullptr;
1879 }
1880
1881 // this is legit only if scale & translation (which should be the case at the moment)
1882 if (isStrokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04001883 params.fInnerXRadius = params.fXRadius - scaledStroke.fX;
1884 params.fInnerYRadius = params.fYRadius - scaledStroke.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001885 }
1886
Brian Salomon05441c42017-05-15 16:45:49 -04001887 params.fXRadius += scaledStroke.fX;
1888 params.fYRadius += scaledStroke.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001889 }
Robert Phillips7c525e62018-06-12 10:11:12 -04001890 return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix,
1891 params, stroke);
Brian Salomon05441c42017-05-15 16:45:49 -04001892 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07001893
Brian Salomonea26d6b2018-01-23 20:33:21 +00001894 EllipseOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkMatrix& viewMatrix,
1895 const DeviceSpaceParams& params, const SkStrokeRec& stroke)
1896 : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
Brian Salomon05441c42017-05-15 16:45:49 -04001897 SkStrokeRec::Style style = stroke.getStyle();
1898 bool isStrokeOnly =
1899 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bsalomon4b4a7cc2016-07-08 04:42:54 -07001900
Brian Salomon05441c42017-05-15 16:45:49 -04001901 fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius,
1902 params.fInnerXRadius, params.fInnerYRadius,
1903 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius,
1904 params.fCenter.fY - params.fYRadius,
1905 params.fCenter.fX + params.fXRadius,
1906 params.fCenter.fY + params.fYRadius)});
1907
1908 this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07001909
bsalomon4b4a7cc2016-07-08 04:42:54 -07001910 // Outset bounds to include half-pixel width antialiasing.
Brian Salomon05441c42017-05-15 16:45:49 -04001911 fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf);
bsalomon4b4a7cc2016-07-08 04:42:54 -07001912
Brian Salomon05441c42017-05-15 16:45:49 -04001913 fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0;
1914 fViewMatrixIfUsingLocalCoords = viewMatrix;
bsalomoncdaa97b2016-03-08 08:30:14 -08001915 }
joshualitt76e7fb62015-02-11 08:52:27 -08001916
Brian Salomon289e3d82016-12-14 15:52:56 -05001917 const char* name() const override { return "EllipseOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08001918
Robert Phillipsf1748f52017-09-14 14:11:24 -04001919 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04001920 fHelper.visitProxies(func);
1921 }
1922
Brian Salomon7c3e7182016-12-01 09:35:30 -05001923 SkString dumpInfo() const override {
1924 SkString string;
1925 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04001926 for (const auto& geo : fEllipses) {
Brian Salomon289e3d82016-12-14 15:52:56 -05001927 string.appendf(
1928 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
1929 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
1930 geo.fColor, geo.fDevBounds.fLeft, geo.fDevBounds.fTop, geo.fDevBounds.fRight,
1931 geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, geo.fInnerXRadius,
1932 geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05001933 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04001934 string += fHelper.dumpInfo();
1935 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05001936 return string;
1937 }
1938
Brian Osman9a725dd2017-09-20 09:53:22 -04001939 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
1940 GrPixelConfigIsClamped dstIsClamped) override {
Brian Salomon05441c42017-05-15 16:45:49 -04001941 GrColor* color = &fEllipses.front().fColor;
Brian Osman9a725dd2017-09-20 09:53:22 -04001942 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
1943 GrProcessorAnalysisCoverage::kSingleChannel, color);
Brian Salomon05441c42017-05-15 16:45:49 -04001944 }
1945
1946 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
1947
bsalomone46f9fe2015-08-18 06:05:14 -07001948private:
Brian Salomon91326c32017-08-09 16:02:19 -04001949 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08001950 SkMatrix localMatrix;
1951 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08001952 return;
1953 }
1954
1955 // Setup geometry processor
Brian Salomonea26d6b2018-01-23 20:33:21 +00001956 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08001957
bsalomonb5238a72015-05-05 07:49:49 -07001958 QuadHelper helper;
Brian Salomon92be2f72018-06-19 14:33:47 -04001959 SkASSERT(sizeof(EllipseVertex) == gp->debugOnly_vertexStride());
Brian Salomon05441c42017-05-15 16:45:49 -04001960 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(
Brian Salomon92be2f72018-06-19 14:33:47 -04001961 helper.init(target, sizeof(EllipseVertex), fEllipses.count()));
bsalomonb5238a72015-05-05 07:49:49 -07001962 if (!verts) {
joshualitt4b31de82015-03-05 14:33:41 -08001963 return;
1964 }
1965
Brian Salomon05441c42017-05-15 16:45:49 -04001966 for (const auto& ellipse : fEllipses) {
1967 GrColor color = ellipse.fColor;
1968 SkScalar xRadius = ellipse.fXRadius;
1969 SkScalar yRadius = ellipse.fYRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08001970
1971 // Compute the reciprocals of the radii here to save time in the shader
1972 SkScalar xRadRecip = SkScalarInvert(xRadius);
1973 SkScalar yRadRecip = SkScalarInvert(yRadius);
Brian Salomon05441c42017-05-15 16:45:49 -04001974 SkScalar xInnerRadRecip = SkScalarInvert(ellipse.fInnerXRadius);
1975 SkScalar yInnerRadRecip = SkScalarInvert(ellipse.fInnerYRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08001976
vjiaoblack977996d2016-06-30 12:20:54 -07001977 // fOffsets are expanded from xyRadii to include the half-pixel antialiasing width.
1978 SkScalar xMaxOffset = xRadius + SK_ScalarHalf;
1979 SkScalar yMaxOffset = yRadius + SK_ScalarHalf;
1980
joshualitt76e7fb62015-02-11 08:52:27 -08001981 // The inner radius in the vertex data must be specified in normalized space.
Brian Salomon05441c42017-05-15 16:45:49 -04001982 verts[0].fPos = SkPoint::Make(ellipse.fDevBounds.fLeft, ellipse.fDevBounds.fTop);
brianosmanbb2ff942016-02-11 14:15:18 -08001983 verts[0].fColor = color;
vjiaoblack977996d2016-06-30 12:20:54 -07001984 verts[0].fOffset = SkPoint::Make(-xMaxOffset, -yMaxOffset);
joshualitt76e7fb62015-02-11 08:52:27 -08001985 verts[0].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
1986 verts[0].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
1987
Brian Salomon05441c42017-05-15 16:45:49 -04001988 verts[1].fPos = SkPoint::Make(ellipse.fDevBounds.fLeft, ellipse.fDevBounds.fBottom);
brianosmanbb2ff942016-02-11 14:15:18 -08001989 verts[1].fColor = color;
vjiaoblack977996d2016-06-30 12:20:54 -07001990 verts[1].fOffset = SkPoint::Make(-xMaxOffset, yMaxOffset);
joshualitt76e7fb62015-02-11 08:52:27 -08001991 verts[1].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
1992 verts[1].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
1993
Brian Salomon57caa662017-10-18 12:21:05 +00001994 verts[2].fPos = SkPoint::Make(ellipse.fDevBounds.fRight, ellipse.fDevBounds.fTop);
brianosmanbb2ff942016-02-11 14:15:18 -08001995 verts[2].fColor = color;
Brian Salomon57caa662017-10-18 12:21:05 +00001996 verts[2].fOffset = SkPoint::Make(xMaxOffset, -yMaxOffset);
joshualitt76e7fb62015-02-11 08:52:27 -08001997 verts[2].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
1998 verts[2].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
1999
Brian Salomon57caa662017-10-18 12:21:05 +00002000 verts[3].fPos = SkPoint::Make(ellipse.fDevBounds.fRight, ellipse.fDevBounds.fBottom);
brianosmanbb2ff942016-02-11 14:15:18 -08002001 verts[3].fColor = color;
Brian Salomon57caa662017-10-18 12:21:05 +00002002 verts[3].fOffset = SkPoint::Make(xMaxOffset, yMaxOffset);
joshualitt76e7fb62015-02-11 08:52:27 -08002003 verts[3].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
2004 verts[3].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
2005
bsalomonb5238a72015-05-05 07:49:49 -07002006 verts += kVerticesPerQuad;
joshualitt76e7fb62015-02-11 08:52:27 -08002007 }
Brian Salomon05441c42017-05-15 16:45:49 -04002008 helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
joshualitt76e7fb62015-02-11 08:52:27 -08002009 }
2010
Brian Salomon25a88092016-12-01 09:36:50 -05002011 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002012 EllipseOp* that = t->cast<EllipseOp>();
bsalomonabd30f52015-08-13 13:34:48 -07002013
Brian Salomon05441c42017-05-15 16:45:49 -04002014 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
joshualitt8cab9a72015-07-16 09:13:50 -07002015 return false;
2016 }
2017
bsalomoncdaa97b2016-03-08 08:30:14 -08002018 if (fStroked != that->fStroked) {
joshualitt76e7fb62015-02-11 08:52:27 -08002019 return false;
2020 }
2021
Brian Salomon05441c42017-05-15 16:45:49 -04002022 if (fHelper.usesLocalCoords() &&
2023 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002024 return false;
2025 }
2026
Brian Salomon05441c42017-05-15 16:45:49 -04002027 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
bsalomon88cf17d2016-07-08 06:40:56 -07002028 this->joinBounds(*that);
joshualitt76e7fb62015-02-11 08:52:27 -08002029 return true;
2030 }
2031
Brian Salomon05441c42017-05-15 16:45:49 -04002032 struct Ellipse {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002033 GrColor fColor;
2034 SkScalar fXRadius;
2035 SkScalar fYRadius;
2036 SkScalar fInnerXRadius;
2037 SkScalar fInnerYRadius;
2038 SkRect fDevBounds;
2039 };
joshualitt76e7fb62015-02-11 08:52:27 -08002040
Brian Salomon289e3d82016-12-14 15:52:56 -05002041 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002042 Helper fHelper;
2043 bool fStroked;
2044 SkSTArray<1, Ellipse, true> fEllipses;
reed1b55a962015-09-17 20:16:13 -07002045
Brian Salomon05441c42017-05-15 16:45:49 -04002046 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002047};
2048
joshualitt76e7fb62015-02-11 08:52:27 -08002049/////////////////////////////////////////////////////////////////////////////////////////////////
2050
Brian Salomon05441c42017-05-15 16:45:49 -04002051class DIEllipseOp : public GrMeshDrawOp {
2052private:
2053 using Helper = GrSimpleMeshDrawOpHelper;
2054
2055 struct DeviceSpaceParams {
2056 SkPoint fCenter;
2057 SkScalar fXRadius;
2058 SkScalar fYRadius;
2059 SkScalar fInnerXRadius;
2060 SkScalar fInnerYRadius;
2061 DIEllipseStyle fStyle;
2062 };
2063
joshualitt76e7fb62015-02-11 08:52:27 -08002064public:
Brian Salomon25a88092016-12-01 09:36:50 -05002065 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002066
Robert Phillips7c525e62018-06-12 10:11:12 -04002067 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
2068 GrPaint&& paint,
2069 const SkMatrix& viewMatrix,
2070 const SkRect& ellipse,
2071 const SkStrokeRec& stroke) {
Brian Salomon05441c42017-05-15 16:45:49 -04002072 DeviceSpaceParams params;
2073 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
2074 params.fXRadius = SkScalarHalf(ellipse.width());
2075 params.fYRadius = SkScalarHalf(ellipse.height());
joshualitt76e7fb62015-02-11 08:52:27 -08002076
bsalomon4b4a7cc2016-07-08 04:42:54 -07002077 SkStrokeRec::Style style = stroke.getStyle();
Brian Salomon05441c42017-05-15 16:45:49 -04002078 params.fStyle = (SkStrokeRec::kStroke_Style == style)
2079 ? DIEllipseStyle::kStroke
2080 : (SkStrokeRec::kHairline_Style == style)
2081 ? DIEllipseStyle::kHairline
2082 : DIEllipseStyle::kFill;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002083
Brian Salomon05441c42017-05-15 16:45:49 -04002084 params.fInnerXRadius = 0;
2085 params.fInnerYRadius = 0;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002086 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) {
2087 SkScalar strokeWidth = stroke.getWidth();
2088
2089 if (SkScalarNearlyZero(strokeWidth)) {
2090 strokeWidth = SK_ScalarHalf;
2091 } else {
2092 strokeWidth *= SK_ScalarHalf;
2093 }
2094
2095 // we only handle thick strokes for near-circular ellipses
2096 if (strokeWidth > SK_ScalarHalf &&
Brian Salomon05441c42017-05-15 16:45:49 -04002097 (SK_ScalarHalf * params.fXRadius > params.fYRadius ||
2098 SK_ScalarHalf * params.fYRadius > params.fXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002099 return nullptr;
2100 }
2101
2102 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon05441c42017-05-15 16:45:49 -04002103 if (strokeWidth * (params.fYRadius * params.fYRadius) <
2104 (strokeWidth * strokeWidth) * params.fXRadius) {
2105 return nullptr;
2106 }
2107 if (strokeWidth * (params.fXRadius * params.fXRadius) <
2108 (strokeWidth * strokeWidth) * params.fYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002109 return nullptr;
2110 }
2111
2112 // set inner radius (if needed)
2113 if (SkStrokeRec::kStroke_Style == style) {
Brian Salomon05441c42017-05-15 16:45:49 -04002114 params.fInnerXRadius = params.fXRadius - strokeWidth;
2115 params.fInnerYRadius = params.fYRadius - strokeWidth;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002116 }
2117
Brian Salomon05441c42017-05-15 16:45:49 -04002118 params.fXRadius += strokeWidth;
2119 params.fYRadius += strokeWidth;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002120 }
Brian Salomon05441c42017-05-15 16:45:49 -04002121 if (DIEllipseStyle::kStroke == params.fStyle &&
2122 (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) {
2123 params.fStyle = DIEllipseStyle::kFill;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002124 }
Robert Phillips7c525e62018-06-12 10:11:12 -04002125 return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), params, viewMatrix);
Brian Salomon05441c42017-05-15 16:45:49 -04002126 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002127
Brian Salomonea26d6b2018-01-23 20:33:21 +00002128 DIEllipseOp(Helper::MakeArgs& helperArgs, GrColor color, const DeviceSpaceParams& params,
2129 const SkMatrix& viewMatrix)
2130 : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002131 // This expands the outer rect so that after CTM we end up with a half-pixel border
2132 SkScalar a = viewMatrix[SkMatrix::kMScaleX];
2133 SkScalar b = viewMatrix[SkMatrix::kMSkewX];
2134 SkScalar c = viewMatrix[SkMatrix::kMSkewY];
2135 SkScalar d = viewMatrix[SkMatrix::kMScaleY];
Brian Salomon289e3d82016-12-14 15:52:56 -05002136 SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c);
2137 SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002138
Brian Salomon05441c42017-05-15 16:45:49 -04002139 fEllipses.emplace_back(
2140 Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius,
2141 params.fInnerYRadius, geoDx, geoDy, params.fStyle,
2142 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius - geoDx,
2143 params.fCenter.fY - params.fYRadius - geoDy,
2144 params.fCenter.fX + params.fXRadius + geoDx,
2145 params.fCenter.fY + params.fYRadius + geoDy)});
2146 this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes,
2147 IsZeroArea::kNo);
joshualitt76e7fb62015-02-11 08:52:27 -08002148 }
2149
Brian Salomon289e3d82016-12-14 15:52:56 -05002150 const char* name() const override { return "DIEllipseOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002151
Robert Phillipsf1748f52017-09-14 14:11:24 -04002152 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002153 fHelper.visitProxies(func);
2154 }
2155
Brian Salomon7c3e7182016-12-01 09:35:30 -05002156 SkString dumpInfo() const override {
2157 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002158 for (const auto& geo : fEllipses) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002159 string.appendf(
2160 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, "
2161 "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, "
2162 "GeoDY: %.2f\n",
2163 geo.fColor, geo.fBounds.fLeft, geo.fBounds.fTop, geo.fBounds.fRight,
2164 geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius, geo.fInnerXRadius,
2165 geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002166 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002167 string += fHelper.dumpInfo();
2168 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002169 return string;
2170 }
2171
Brian Osman9a725dd2017-09-20 09:53:22 -04002172 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
2173 GrPixelConfigIsClamped dstIsClamped) override {
Brian Salomon05441c42017-05-15 16:45:49 -04002174 GrColor* color = &fEllipses.front().fColor;
Brian Osman9a725dd2017-09-20 09:53:22 -04002175 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
2176 GrProcessorAnalysisCoverage::kSingleChannel, color);
Brian Salomon05441c42017-05-15 16:45:49 -04002177 }
2178
2179 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2180
bsalomone46f9fe2015-08-18 06:05:14 -07002181private:
Brian Salomon91326c32017-08-09 16:02:19 -04002182 void onPrepareDraws(Target* target) override {
joshualitt76e7fb62015-02-11 08:52:27 -08002183 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002184 sk_sp<GrGeometryProcessor> gp(
Brian Salomonea26d6b2018-01-23 20:33:21 +00002185 new DIEllipseGeometryProcessor(this->viewMatrix(), this->style()));
joshualitt76e7fb62015-02-11 08:52:27 -08002186
Brian Salomon92be2f72018-06-19 14:33:47 -04002187 SkASSERT(sizeof(DIEllipseVertex) == gp->debugOnly_vertexStride());
bsalomonb5238a72015-05-05 07:49:49 -07002188 QuadHelper helper;
2189 DIEllipseVertex* verts = reinterpret_cast<DIEllipseVertex*>(
Brian Salomon92be2f72018-06-19 14:33:47 -04002190 helper.init(target, sizeof(DIEllipseVertex), fEllipses.count()));
bsalomonb5238a72015-05-05 07:49:49 -07002191 if (!verts) {
joshualitt4b31de82015-03-05 14:33:41 -08002192 return;
2193 }
2194
Brian Salomon05441c42017-05-15 16:45:49 -04002195 for (const auto& ellipse : fEllipses) {
2196 GrColor color = ellipse.fColor;
2197 SkScalar xRadius = ellipse.fXRadius;
2198 SkScalar yRadius = ellipse.fYRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002199
Brian Salomon05441c42017-05-15 16:45:49 -04002200 const SkRect& bounds = ellipse.fBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002201
2202 // This adjusts the "radius" to include the half-pixel border
Brian Salomon05441c42017-05-15 16:45:49 -04002203 SkScalar offsetDx = ellipse.fGeoDx / xRadius;
2204 SkScalar offsetDy = ellipse.fGeoDy / yRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002205
joshualitt76e7fb62015-02-11 08:52:27 -08002206 verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop);
brianosmanbb2ff942016-02-11 14:15:18 -08002207 verts[0].fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002208 verts[0].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, -1.0f - offsetDy);
Greg Daniel75a13022018-04-04 08:59:20 -04002209 verts[0].fInnerOffset = SkPoint::Make(0.0f, 0.0f);
joshualitt76e7fb62015-02-11 08:52:27 -08002210
Brian Salomon289e3d82016-12-14 15:52:56 -05002211 verts[1].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom);
brianosmanbb2ff942016-02-11 14:15:18 -08002212 verts[1].fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002213 verts[1].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, 1.0f + offsetDy);
Greg Daniel75a13022018-04-04 08:59:20 -04002214 verts[1].fInnerOffset = SkPoint::Make(0.0f, 0.0f);
joshualitt76e7fb62015-02-11 08:52:27 -08002215
Brian Salomon57caa662017-10-18 12:21:05 +00002216 verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
brianosmanbb2ff942016-02-11 14:15:18 -08002217 verts[2].fColor = color;
Brian Salomon57caa662017-10-18 12:21:05 +00002218 verts[2].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offsetDy);
Greg Daniel75a13022018-04-04 08:59:20 -04002219 verts[2].fInnerOffset = SkPoint::Make(0.0f, 0.0f);
joshualitt76e7fb62015-02-11 08:52:27 -08002220
Brian Salomon57caa662017-10-18 12:21:05 +00002221 verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
brianosmanbb2ff942016-02-11 14:15:18 -08002222 verts[3].fColor = color;
Brian Salomon57caa662017-10-18 12:21:05 +00002223 verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, 1.0f + offsetDy);
Greg Daniel75a13022018-04-04 08:59:20 -04002224 verts[3].fInnerOffset = SkPoint::Make(0.0f, 0.0f);
2225
2226 if (DIEllipseStyle::kStroke == this->style()) {
2227 SkScalar innerRatioX = xRadius / ellipse.fInnerXRadius;
2228 SkScalar innerRatioY = yRadius / ellipse.fInnerYRadius;
2229
2230 verts[0].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx,
2231 -innerRatioY - offsetDy);
2232 verts[1].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx,
2233 innerRatioY + offsetDy);
2234 verts[2].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx,
2235 -innerRatioY - offsetDy);
2236 verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx,
2237 innerRatioY + offsetDy);
2238 }
joshualitt76e7fb62015-02-11 08:52:27 -08002239
bsalomonb5238a72015-05-05 07:49:49 -07002240 verts += kVerticesPerQuad;
joshualitt76e7fb62015-02-11 08:52:27 -08002241 }
Brian Salomon05441c42017-05-15 16:45:49 -04002242 helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
joshualitt76e7fb62015-02-11 08:52:27 -08002243 }
halcanary9d524f22016-03-29 09:03:52 -07002244
Brian Salomon25a88092016-12-01 09:36:50 -05002245 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002246 DIEllipseOp* that = t->cast<DIEllipseOp>();
Brian Salomon05441c42017-05-15 16:45:49 -04002247 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
joshualitt8cab9a72015-07-16 09:13:50 -07002248 return false;
2249 }
2250
bsalomoncdaa97b2016-03-08 08:30:14 -08002251 if (this->style() != that->style()) {
joshualitt76e7fb62015-02-11 08:52:27 -08002252 return false;
2253 }
2254
joshualittd96a67b2015-05-05 14:09:05 -07002255 // TODO rewrite to allow positioning on CPU
2256 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
joshualitt76e7fb62015-02-11 08:52:27 -08002257 return false;
2258 }
2259
Brian Salomon05441c42017-05-15 16:45:49 -04002260 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin());
bsalomon88cf17d2016-07-08 06:40:56 -07002261 this->joinBounds(*that);
joshualitt76e7fb62015-02-11 08:52:27 -08002262 return true;
2263 }
2264
Brian Salomon05441c42017-05-15 16:45:49 -04002265 const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; }
2266 DIEllipseStyle style() const { return fEllipses[0].fStyle; }
joshualitt76e7fb62015-02-11 08:52:27 -08002267
Brian Salomon05441c42017-05-15 16:45:49 -04002268 struct Ellipse {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002269 SkMatrix fViewMatrix;
2270 GrColor fColor;
2271 SkScalar fXRadius;
2272 SkScalar fYRadius;
2273 SkScalar fInnerXRadius;
2274 SkScalar fInnerYRadius;
2275 SkScalar fGeoDx;
2276 SkScalar fGeoDy;
2277 DIEllipseStyle fStyle;
2278 SkRect fBounds;
2279 };
2280
Brian Salomon05441c42017-05-15 16:45:49 -04002281 Helper fHelper;
2282 SkSTArray<1, Ellipse, true> fEllipses;
reed1b55a962015-09-17 20:16:13 -07002283
Brian Salomon05441c42017-05-15 16:45:49 -04002284 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002285};
2286
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002287///////////////////////////////////////////////////////////////////////////////
2288
Mike Kleinfc6c37b2016-09-27 09:34:10 -04002289// We have three possible cases for geometry for a roundrect.
jvanverthc3d0e422016-08-25 08:12:35 -07002290//
2291// In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch:
2292// ____________
2293// |_|________|_|
2294// | | | |
2295// | | | |
2296// | | | |
2297// |_|________|_|
2298// |_|________|_|
2299//
2300// For strokes, we don't draw the center quad.
2301//
2302// For circular roundrects, in the case where the stroke width is greater than twice
2303// the corner radius (overstroke), we add additional geometry to mark out the rectangle
jvanverth6a397612016-08-26 08:15:33 -07002304// in the center. The shared vertices are duplicated so we can set a different outer radius
2305// for the fill calculation.
jvanverthc3d0e422016-08-25 08:12:35 -07002306// ____________
2307// |_|________|_|
2308// | |\ ____ /| |
2309// | | | | | |
2310// | | |____| | |
2311// |_|/______\|_|
2312// |_|________|_|
2313//
2314// We don't draw the center quad from the fill rect in this case.
Robert Phillips79839d42016-10-06 15:03:34 -04002315//
2316// For filled rrects that need to provide a distance vector we resuse the overstroke
2317// geometry but make the inner rect degenerate (either a point or a horizontal or
2318// vertical line).
jvanverthc3d0e422016-08-25 08:12:35 -07002319
jvanverth84839f62016-08-29 10:16:40 -07002320static const uint16_t gOverstrokeRRectIndices[] = {
Brian Salomon289e3d82016-12-14 15:52:56 -05002321 // clang-format off
2322 // overstroke quads
2323 // we place this at the beginning so that we can skip these indices when rendering normally
2324 16, 17, 19, 16, 19, 18,
2325 19, 17, 23, 19, 23, 21,
2326 21, 23, 22, 21, 22, 20,
2327 22, 16, 18, 22, 18, 20,
jvanverthc3d0e422016-08-25 08:12:35 -07002328
Brian Salomon289e3d82016-12-14 15:52:56 -05002329 // corners
2330 0, 1, 5, 0, 5, 4,
2331 2, 3, 7, 2, 7, 6,
2332 8, 9, 13, 8, 13, 12,
2333 10, 11, 15, 10, 15, 14,
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +00002334
Brian Salomon289e3d82016-12-14 15:52:56 -05002335 // edges
2336 1, 2, 6, 1, 6, 5,
2337 4, 5, 9, 4, 9, 8,
2338 6, 7, 11, 6, 11, 10,
2339 9, 10, 14, 9, 14, 13,
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +00002340
Brian Salomon289e3d82016-12-14 15:52:56 -05002341 // center
2342 // we place this at the end so that we can ignore these indices when not rendering as filled
2343 5, 6, 10, 5, 10, 9,
2344 // clang-format on
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002345};
Brian Salomon289e3d82016-12-14 15:52:56 -05002346
jvanverth84839f62016-08-29 10:16:40 -07002347// fill and standard stroke indices skip the overstroke "ring"
Brian Salomon289e3d82016-12-14 15:52:56 -05002348static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002349
jvanverth84839f62016-08-29 10:16:40 -07002350// overstroke count is arraysize minus the center indices
2351static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6;
2352// fill count skips overstroke indices and includes center
Brian Salomon289e3d82016-12-14 15:52:56 -05002353static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
jvanverth84839f62016-08-29 10:16:40 -07002354// stroke count is fill count minus center indices
jvanverthc3d0e422016-08-25 08:12:35 -07002355static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6;
2356static const int kVertsPerStandardRRect = 16;
jvanverth6a397612016-08-26 08:15:33 -07002357static const int kVertsPerOverstrokeRRect = 24;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00002358
jvanverthc3d0e422016-08-25 08:12:35 -07002359enum RRectType {
2360 kFill_RRectType,
2361 kStroke_RRectType,
jvanverth84839f62016-08-29 10:16:40 -07002362 kOverstroke_RRectType,
jvanverthc3d0e422016-08-25 08:12:35 -07002363};
2364
jvanverth84839f62016-08-29 10:16:40 -07002365static int rrect_type_to_vert_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002366 switch (type) {
2367 case kFill_RRectType:
2368 case kStroke_RRectType:
2369 return kVertsPerStandardRRect;
2370 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002371 return kVertsPerOverstrokeRRect;
2372 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002373 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002374 return 0;
jvanverth84839f62016-08-29 10:16:40 -07002375}
2376
2377static int rrect_type_to_index_count(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002378 switch (type) {
2379 case kFill_RRectType:
2380 return kIndicesPerFillRRect;
2381 case kStroke_RRectType:
2382 return kIndicesPerStrokeRRect;
2383 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002384 return kIndicesPerOverstrokeRRect;
2385 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002386 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002387 return 0;
jvanverth84839f62016-08-29 10:16:40 -07002388}
2389
2390static const uint16_t* rrect_type_to_indices(RRectType type) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002391 switch (type) {
2392 case kFill_RRectType:
2393 case kStroke_RRectType:
2394 return gStandardRRectIndices;
2395 case kOverstroke_RRectType:
Brian Salomon289e3d82016-12-14 15:52:56 -05002396 return gOverstrokeRRectIndices;
2397 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002398 SK_ABORT("Invalid type");
Brian Salomon289e3d82016-12-14 15:52:56 -05002399 return 0;
bsalomoned0bcad2015-05-04 10:36:42 -07002400}
2401
joshualitt76e7fb62015-02-11 08:52:27 -08002402///////////////////////////////////////////////////////////////////////////////////////////////////
2403
Robert Phillips79839d42016-10-06 15:03:34 -04002404// For distance computations in the interior of filled rrects we:
2405//
2406// add a interior degenerate (point or line) rect
2407// each vertex of that rect gets -outerRad as its radius
2408// this makes the computation of the distance to the outer edge be negative
2409// negative values are caught and then handled differently in the GP's onEmitCode
2410// each vertex is also given the normalized x & y distance from the interior rect's edge
2411// the GP takes the min of those depths +1 to get the normalized distance to the outer edge
2412
Brian Salomon05441c42017-05-15 16:45:49 -04002413class CircularRRectOp : public GrMeshDrawOp {
2414private:
2415 using Helper = GrSimpleMeshDrawOpHelper;
2416
joshualitt76e7fb62015-02-11 08:52:27 -08002417public:
Brian Salomon25a88092016-12-01 09:36:50 -05002418 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002419
bsalomon4b4a7cc2016-07-08 04:42:54 -07002420 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
2421 // whether the rrect is only stroked or stroked and filled.
Robert Phillips7c525e62018-06-12 10:11:12 -04002422 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
2423 GrPaint&& paint,
2424 const SkMatrix& viewMatrix,
2425 const SkRect& devRect,
2426 float devRadius,
2427 float devStrokeWidth,
2428 bool strokeOnly) {
2429 return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix,
2430 devRect, devRadius,
2431 devStrokeWidth, strokeOnly);
Brian Salomon05441c42017-05-15 16:45:49 -04002432 }
Brian Salomonea26d6b2018-01-23 20:33:21 +00002433 CircularRRectOp(Helper::MakeArgs& helperArgs, GrColor color, const SkMatrix& viewMatrix,
2434 const SkRect& devRect, float devRadius, float devStrokeWidth, bool strokeOnly)
Brian Salomon05441c42017-05-15 16:45:49 -04002435 : INHERITED(ClassID())
2436 , fViewMatrixIfUsingLocalCoords(viewMatrix)
Brian Salomonea26d6b2018-01-23 20:33:21 +00002437 , fHelper(helperArgs, GrAAType::kCoverage) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002438 SkRect bounds = devRect;
2439 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
2440 SkScalar innerRadius = 0.0f;
2441 SkScalar outerRadius = devRadius;
2442 SkScalar halfWidth = 0;
jvanverth84839f62016-08-29 10:16:40 -07002443 RRectType type = kFill_RRectType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002444 if (devStrokeWidth > 0) {
2445 if (SkScalarNearlyZero(devStrokeWidth)) {
2446 halfWidth = SK_ScalarHalf;
2447 } else {
2448 halfWidth = SkScalarHalf(devStrokeWidth);
2449 }
joshualitt76e7fb62015-02-11 08:52:27 -08002450
bsalomon4b4a7cc2016-07-08 04:42:54 -07002451 if (strokeOnly) {
jvanverthc3d0e422016-08-25 08:12:35 -07002452 // Outset stroke by 1/4 pixel
2453 devStrokeWidth += 0.25f;
2454 // If stroke is greater than width or height, this is still a fill
2455 // Otherwise we compute stroke params
Brian Salomon289e3d82016-12-14 15:52:56 -05002456 if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) {
jvanverthc3d0e422016-08-25 08:12:35 -07002457 innerRadius = devRadius - halfWidth;
jvanverth84839f62016-08-29 10:16:40 -07002458 type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
jvanverthc3d0e422016-08-25 08:12:35 -07002459 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002460 }
2461 outerRadius += halfWidth;
2462 bounds.outset(halfWidth, halfWidth);
2463 }
bsalomoncdaa97b2016-03-08 08:30:14 -08002464
bsalomon4b4a7cc2016-07-08 04:42:54 -07002465 // The radii are outset for two reasons. First, it allows the shader to simply perform
2466 // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
2467 // Second, the outer radius is used to compute the verts of the bounding box that is
2468 // rendered and the outset ensures the box will cover all partially covered by the rrect
2469 // corners.
2470 outerRadius += SK_ScalarHalf;
2471 innerRadius -= SK_ScalarHalf;
2472
bsalomon88cf17d2016-07-08 06:40:56 -07002473 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
2474
2475 // Expand the rect for aa to generate correct vertices.
bsalomon4b4a7cc2016-07-08 04:42:54 -07002476 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
2477
Brian Salomon05441c42017-05-15 16:45:49 -04002478 fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type});
jvanverth84839f62016-08-29 10:16:40 -07002479 fVertCount = rrect_type_to_vert_count(type);
2480 fIndexCount = rrect_type_to_index_count(type);
2481 fAllFill = (kFill_RRectType == type);
joshualitt76e7fb62015-02-11 08:52:27 -08002482 }
2483
Brian Salomon289e3d82016-12-14 15:52:56 -05002484 const char* name() const override { return "CircularRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002485
Robert Phillipsf1748f52017-09-14 14:11:24 -04002486 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002487 fHelper.visitProxies(func);
2488 }
2489
jvanverthc3d0e422016-08-25 08:12:35 -07002490 SkString dumpInfo() const override {
2491 SkString string;
Brian Salomon05441c42017-05-15 16:45:49 -04002492 for (int i = 0; i < fRRects.count(); ++i) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002493 string.appendf(
2494 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
2495 "InnerRad: %.2f, OuterRad: %.2f\n",
Brian Salomon05441c42017-05-15 16:45:49 -04002496 fRRects[i].fColor, fRRects[i].fDevBounds.fLeft, fRRects[i].fDevBounds.fTop,
2497 fRRects[i].fDevBounds.fRight, fRRects[i].fDevBounds.fBottom,
2498 fRRects[i].fInnerRadius, fRRects[i].fOuterRadius);
jvanverthc3d0e422016-08-25 08:12:35 -07002499 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002500 string += fHelper.dumpInfo();
2501 string += INHERITED::dumpInfo();
jvanverthc3d0e422016-08-25 08:12:35 -07002502 return string;
2503 }
2504
Brian Osman9a725dd2017-09-20 09:53:22 -04002505 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
2506 GrPixelConfigIsClamped dstIsClamped) override {
Brian Salomon05441c42017-05-15 16:45:49 -04002507 GrColor* color = &fRRects.front().fColor;
Brian Osman9a725dd2017-09-20 09:53:22 -04002508 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
2509 GrProcessorAnalysisCoverage::kSingleChannel, color);
Brian Salomon05441c42017-05-15 16:45:49 -04002510 }
2511
2512 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2513
Brian Salomon92aee3d2016-12-21 09:20:25 -05002514private:
Robert Phillips79839d42016-10-06 15:03:34 -04002515 struct CircleVertex {
Brian Salomon289e3d82016-12-14 15:52:56 -05002516 SkPoint fPos;
2517 GrColor fColor;
2518 SkPoint fOffset;
Robert Phillips79839d42016-10-06 15:03:34 -04002519 SkScalar fOuterRadius;
2520 SkScalar fInnerRadius;
2521 // No half plane, we don't use it here.
2522 };
2523
Brian Salomon289e3d82016-12-14 15:52:56 -05002524 static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds, SkScalar smInset,
2525 SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius,
2526 SkScalar innerRadius, GrColor color) {
Robert Phillips79839d42016-10-06 15:03:34 -04002527 SkASSERT(smInset < bigInset);
2528
2529 // TL
2530 (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset);
2531 (*verts)->fColor = color;
2532 (*verts)->fOffset = SkPoint::Make(xOffset, 0);
2533 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002534 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002535 (*verts)++;
2536
2537 // TR
Brian Salomon289e3d82016-12-14 15:52:56 -05002538 (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fTop + smInset);
Robert Phillips79839d42016-10-06 15:03:34 -04002539 (*verts)->fColor = color;
2540 (*verts)->fOffset = SkPoint::Make(xOffset, 0);
2541 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002542 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002543 (*verts)++;
2544
Brian Salomon289e3d82016-12-14 15:52:56 -05002545 (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fTop + bigInset);
Robert Phillips79839d42016-10-06 15:03:34 -04002546 (*verts)->fColor = color;
2547 (*verts)->fOffset = SkPoint::Make(0, 0);
2548 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002549 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002550 (*verts)++;
2551
Brian Salomon289e3d82016-12-14 15:52:56 -05002552 (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fTop + bigInset);
Robert Phillips79839d42016-10-06 15:03:34 -04002553 (*verts)->fColor = color;
2554 (*verts)->fOffset = SkPoint::Make(0, 0);
2555 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002556 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002557 (*verts)++;
2558
2559 (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fBottom - bigInset);
2560 (*verts)->fColor = color;
2561 (*verts)->fOffset = SkPoint::Make(0, 0);
2562 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002563 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002564 (*verts)++;
2565
2566 (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fBottom - bigInset);
2567 (*verts)->fColor = color;
2568 (*verts)->fOffset = SkPoint::Make(0, 0);
2569 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002570 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002571 (*verts)++;
2572
2573 // BL
2574 (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset);
2575 (*verts)->fColor = color;
2576 (*verts)->fOffset = SkPoint::Make(xOffset, 0);
2577 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002578 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002579 (*verts)++;
2580
2581 // BR
2582 (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset);
2583 (*verts)->fColor = color;
2584 (*verts)->fOffset = SkPoint::Make(xOffset, 0);
2585 (*verts)->fOuterRadius = outerRadius;
Robert Phillips3786c772016-10-06 17:38:46 -04002586 (*verts)->fInnerRadius = innerRadius;
Robert Phillips79839d42016-10-06 15:03:34 -04002587 (*verts)++;
2588 }
2589
Brian Salomon91326c32017-08-09 16:02:19 -04002590 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002591 // Invert the view matrix as a local matrix (if any other processors require coords).
2592 SkMatrix localMatrix;
2593 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002594 return;
2595 }
2596
2597 // Setup geometry processor
Brian Salomon289e3d82016-12-14 15:52:56 -05002598 sk_sp<GrGeometryProcessor> gp(
Brian Salomon45c92202018-04-10 10:53:58 -04002599 new CircleGeometryProcessor(!fAllFill, false, false, false, false, localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002600
Brian Salomon92be2f72018-06-19 14:33:47 -04002601 SkASSERT(sizeof(CircleVertex) == gp->debugOnly_vertexStride());
joshualitt76e7fb62015-02-11 08:52:27 -08002602
jvanverth84839f62016-08-29 10:16:40 -07002603 const GrBuffer* vertexBuffer;
2604 int firstVertex;
joshualitt76e7fb62015-02-11 08:52:27 -08002605
Brian Salomon92be2f72018-06-19 14:33:47 -04002606 CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(
2607 sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex);
jvanverth84839f62016-08-29 10:16:40 -07002608 if (!verts) {
joshualitt4b31de82015-03-05 14:33:41 -08002609 SkDebugf("Could not allocate vertices\n");
2610 return;
2611 }
2612
jvanverth84839f62016-08-29 10:16:40 -07002613 const GrBuffer* indexBuffer = nullptr;
2614 int firstIndex = 0;
2615 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
2616 if (!indices) {
2617 SkDebugf("Could not allocate indices\n");
2618 return;
2619 }
2620
2621 int currStartVertex = 0;
Brian Salomon05441c42017-05-15 16:45:49 -04002622 for (const auto& rrect : fRRects) {
2623 GrColor color = rrect.fColor;
2624 SkScalar outerRadius = rrect.fOuterRadius;
2625 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002626
Brian Salomon289e3d82016-12-14 15:52:56 -05002627 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius,
2628 bounds.fBottom - outerRadius, bounds.fBottom};
joshualitt76e7fb62015-02-11 08:52:27 -08002629
Brian Salomon289e3d82016-12-14 15:52:56 -05002630 SkScalar yOuterRadii[4] = {-1, 0, 0, 1};
joshualitt76e7fb62015-02-11 08:52:27 -08002631 // The inner radius in the vertex data must be specified in normalized space.
jvanverth84839f62016-08-29 10:16:40 -07002632 // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
Brian Salomone23bffd2017-06-02 11:01:10 -04002633 SkScalar innerRadius = rrect.fType != kFill_RRectType
2634 ? rrect.fInnerRadius / rrect.fOuterRadius
2635 : -1.0f / rrect.fOuterRadius;
joshualitt76e7fb62015-02-11 08:52:27 -08002636 for (int i = 0; i < 4; ++i) {
2637 verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002638 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002639 verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
2640 verts->fOuterRadius = outerRadius;
2641 verts->fInnerRadius = innerRadius;
2642 verts++;
2643
2644 verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002645 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002646 verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
2647 verts->fOuterRadius = outerRadius;
2648 verts->fInnerRadius = innerRadius;
2649 verts++;
2650
2651 verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002652 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002653 verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
2654 verts->fOuterRadius = outerRadius;
2655 verts->fInnerRadius = innerRadius;
2656 verts++;
2657
2658 verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002659 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002660 verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
2661 verts->fOuterRadius = outerRadius;
2662 verts->fInnerRadius = innerRadius;
2663 verts++;
2664 }
jvanverthc3d0e422016-08-25 08:12:35 -07002665 // Add the additional vertices for overstroked rrects.
jvanvertha4f1af82016-08-29 07:17:47 -07002666 // Effectively this is an additional stroked rrect, with its
2667 // outer radius = outerRadius - innerRadius, and inner radius = 0.
2668 // This will give us correct AA in the center and the correct
2669 // distance to the outer edge.
jvanverthc3d0e422016-08-25 08:12:35 -07002670 //
jvanvertha4f1af82016-08-29 07:17:47 -07002671 // Also, the outer offset is a constant vector pointing to the right, which
2672 // guarantees that the distance value along the outer rectangle is constant.
Brian Salomon05441c42017-05-15 16:45:49 -04002673 if (kOverstroke_RRectType == rrect.fType) {
2674 SkASSERT(rrect.fInnerRadius <= 0.0f);
Robert Phillips79839d42016-10-06 15:03:34 -04002675
Brian Salomon05441c42017-05-15 16:45:49 -04002676 SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002677 // this is the normalized distance from the outer rectangle of this
2678 // geometry to the outer edge
Brian Salomon05441c42017-05-15 16:45:49 -04002679 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius;
jvanvertha4f1af82016-08-29 07:17:47 -07002680
Brian Salomon289e3d82016-12-14 15:52:56 -05002681 FillInOverstrokeVerts(&verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
Brian Salomon05441c42017-05-15 16:45:49 -04002682 overstrokeOuterRadius, 0.0f, rrect.fColor);
Robert Phillips79839d42016-10-06 15:03:34 -04002683 }
jvanverth6a397612016-08-26 08:15:33 -07002684
Brian Salomon05441c42017-05-15 16:45:49 -04002685 const uint16_t* primIndices = rrect_type_to_indices(rrect.fType);
2686 const int primIndexCount = rrect_type_to_index_count(rrect.fType);
jvanverth84839f62016-08-29 10:16:40 -07002687 for (int i = 0; i < primIndexCount; ++i) {
2688 *indices++ = primIndices[i] + currStartVertex;
2689 }
2690
Brian Salomon05441c42017-05-15 16:45:49 -04002691 currStartVertex += rrect_type_to_vert_count(rrect.fType);
joshualitt76e7fb62015-02-11 08:52:27 -08002692 }
2693
Chris Dalton3809bab2017-06-13 10:55:06 -06002694 GrMesh mesh(GrPrimitiveType::kTriangles);
Brian Salomon802cb312018-06-08 18:05:20 -04002695 mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
2696 GrPrimitiveRestart::kNo);
Chris Dalton114a3c02017-05-26 15:17:19 -06002697 mesh.setVertexData(vertexBuffer, firstVertex);
Brian Salomon05441c42017-05-15 16:45:49 -04002698 target->draw(gp.get(), fHelper.makePipeline(target), mesh);
joshualitt76e7fb62015-02-11 08:52:27 -08002699 }
2700
Brian Salomon25a88092016-12-01 09:36:50 -05002701 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002702 CircularRRectOp* that = t->cast<CircularRRectOp>();
Jim Van Verth8cefe402017-02-09 11:36:37 -05002703
2704 // can only represent 65535 unique vertices with 16-bit indices
Jim Van Verthe549a052017-02-21 17:55:13 -05002705 if (fVertCount + that->fVertCount > 65536) {
Jim Van Verth8cefe402017-02-09 11:36:37 -05002706 return false;
2707 }
2708
Brian Salomon05441c42017-05-15 16:45:49 -04002709 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
joshualitt8cab9a72015-07-16 09:13:50 -07002710 return false;
2711 }
2712
Brian Salomon05441c42017-05-15 16:45:49 -04002713 if (fHelper.usesLocalCoords() &&
2714 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002715 return false;
2716 }
2717
Brian Salomon05441c42017-05-15 16:45:49 -04002718 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
bsalomon88cf17d2016-07-08 06:40:56 -07002719 this->joinBounds(*that);
jvanverth84839f62016-08-29 10:16:40 -07002720 fVertCount += that->fVertCount;
2721 fIndexCount += that->fIndexCount;
2722 fAllFill = fAllFill && that->fAllFill;
joshualitt76e7fb62015-02-11 08:52:27 -08002723 return true;
2724 }
2725
Brian Salomon05441c42017-05-15 16:45:49 -04002726 struct RRect {
Brian Salomon289e3d82016-12-14 15:52:56 -05002727 GrColor fColor;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002728 SkScalar fInnerRadius;
2729 SkScalar fOuterRadius;
2730 SkRect fDevBounds;
jvanverth84839f62016-08-29 10:16:40 -07002731 RRectType fType;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002732 };
2733
Brian Salomon289e3d82016-12-14 15:52:56 -05002734 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002735 Helper fHelper;
Brian Salomon289e3d82016-12-14 15:52:56 -05002736 int fVertCount;
2737 int fIndexCount;
2738 bool fAllFill;
Brian Salomon05441c42017-05-15 16:45:49 -04002739 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002740
Brian Salomon05441c42017-05-15 16:45:49 -04002741 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002742};
2743
jvanverth84839f62016-08-29 10:16:40 -07002744static const int kNumRRectsInIndexBuffer = 256;
2745
2746GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2747GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
Brian Salomond28a79d2017-10-16 13:01:07 -04002748static sk_sp<const GrBuffer> get_rrect_index_buffer(RRectType type,
2749 GrResourceProvider* resourceProvider) {
jvanverth84839f62016-08-29 10:16:40 -07002750 GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey);
2751 GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey);
2752 switch (type) {
2753 case kFill_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002754 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002755 gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer,
2756 kVertsPerStandardRRect, gRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002757 case kStroke_RRectType:
Chris Daltonff926502017-05-03 14:36:54 -04002758 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon289e3d82016-12-14 15:52:56 -05002759 gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer,
2760 kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey);
jvanverth84839f62016-08-29 10:16:40 -07002761 default:
2762 SkASSERT(false);
2763 return nullptr;
2764 };
2765}
2766
Brian Salomon05441c42017-05-15 16:45:49 -04002767class EllipticalRRectOp : public GrMeshDrawOp {
2768private:
2769 using Helper = GrSimpleMeshDrawOpHelper;
2770
joshualitt76e7fb62015-02-11 08:52:27 -08002771public:
Brian Salomon25a88092016-12-01 09:36:50 -05002772 DEFINE_OP_CLASS_ID
reed1b55a962015-09-17 20:16:13 -07002773
bsalomon4b4a7cc2016-07-08 04:42:54 -07002774 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
2775 // whether the rrect is only stroked or stroked and filled.
Robert Phillips7c525e62018-06-12 10:11:12 -04002776 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
2777 GrPaint&& paint,
2778 const SkMatrix& viewMatrix,
2779 const SkRect& devRect,
2780 float devXRadius,
2781 float devYRadius,
2782 SkVector devStrokeWidths,
2783 bool strokeOnly) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002784 SkASSERT(devXRadius > 0.5);
2785 SkASSERT(devYRadius > 0.5);
2786 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0));
2787 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0));
bsalomon4b4a7cc2016-07-08 04:42:54 -07002788 if (devStrokeWidths.fX > 0) {
2789 if (SkScalarNearlyZero(devStrokeWidths.length())) {
2790 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf);
2791 } else {
2792 devStrokeWidths.scale(SK_ScalarHalf);
2793 }
joshualitt76e7fb62015-02-11 08:52:27 -08002794
bsalomon4b4a7cc2016-07-08 04:42:54 -07002795 // we only handle thick strokes for near-circular ellipses
2796 if (devStrokeWidths.length() > SK_ScalarHalf &&
Brian Salomon289e3d82016-12-14 15:52:56 -05002797 (SK_ScalarHalf * devXRadius > devYRadius ||
2798 SK_ScalarHalf * devYRadius > devXRadius)) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002799 return nullptr;
2800 }
2801
2802 // we don't handle it if curvature of the stroke is less than curvature of the ellipse
Brian Salomon289e3d82016-12-14 15:52:56 -05002803 if (devStrokeWidths.fX * (devYRadius * devYRadius) <
2804 (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002805 return nullptr;
2806 }
Brian Salomon289e3d82016-12-14 15:52:56 -05002807 if (devStrokeWidths.fY * (devXRadius * devXRadius) <
2808 (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002809 return nullptr;
2810 }
Brian Salomon05441c42017-05-15 16:45:49 -04002811 }
Robert Phillips7c525e62018-06-12 10:11:12 -04002812 return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint),
2813 viewMatrix, devRect,
Brian Salomon05441c42017-05-15 16:45:49 -04002814 devXRadius, devYRadius, devStrokeWidths,
2815 strokeOnly);
2816 }
bsalomon4b4a7cc2016-07-08 04:42:54 -07002817
Brian Salomonea26d6b2018-01-23 20:33:21 +00002818 EllipticalRRectOp(Helper::MakeArgs helperArgs, GrColor color, const SkMatrix& viewMatrix,
2819 const SkRect& devRect, float devXRadius, float devYRadius,
2820 SkVector devStrokeHalfWidths, bool strokeOnly)
2821 : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
Brian Salomon05441c42017-05-15 16:45:49 -04002822 SkScalar innerXRadius = 0.0f;
2823 SkScalar innerYRadius = 0.0f;
2824 SkRect bounds = devRect;
2825 bool stroked = false;
2826 if (devStrokeHalfWidths.fX > 0) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002827 // this is legit only if scale & translation (which should be the case at the moment)
2828 if (strokeOnly) {
Brian Salomon05441c42017-05-15 16:45:49 -04002829 innerXRadius = devXRadius - devStrokeHalfWidths.fX;
2830 innerYRadius = devYRadius - devStrokeHalfWidths.fY;
bsalomon4b4a7cc2016-07-08 04:42:54 -07002831 stroked = (innerXRadius >= 0 && innerYRadius >= 0);
2832 }
2833
Brian Salomon05441c42017-05-15 16:45:49 -04002834 devXRadius += devStrokeHalfWidths.fX;
2835 devYRadius += devStrokeHalfWidths.fY;
2836 bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY);
bsalomon4b4a7cc2016-07-08 04:42:54 -07002837 }
2838
Brian Salomon05441c42017-05-15 16:45:49 -04002839 fStroked = stroked;
2840 fViewMatrixIfUsingLocalCoords = viewMatrix;
2841 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon88cf17d2016-07-08 06:40:56 -07002842 // Expand the rect for aa in order to generate the correct vertices.
2843 bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
Brian Salomon05441c42017-05-15 16:45:49 -04002844 fRRects.emplace_back(
2845 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
joshualitt76e7fb62015-02-11 08:52:27 -08002846 }
2847
Brian Salomon289e3d82016-12-14 15:52:56 -05002848 const char* name() const override { return "EllipticalRRectOp"; }
joshualitt76e7fb62015-02-11 08:52:27 -08002849
Robert Phillipsf1748f52017-09-14 14:11:24 -04002850 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -04002851 fHelper.visitProxies(func);
2852 }
2853
Brian Salomon7c3e7182016-12-01 09:35:30 -05002854 SkString dumpInfo() const override {
2855 SkString string;
2856 string.appendf("Stroked: %d\n", fStroked);
Brian Salomon05441c42017-05-15 16:45:49 -04002857 for (const auto& geo : fRRects) {
Brian Salomon289e3d82016-12-14 15:52:56 -05002858 string.appendf(
2859 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
2860 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n",
2861 geo.fColor, geo.fDevBounds.fLeft, geo.fDevBounds.fTop, geo.fDevBounds.fRight,
2862 geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, geo.fInnerXRadius,
2863 geo.fInnerYRadius);
Brian Salomon7c3e7182016-12-01 09:35:30 -05002864 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -04002865 string += fHelper.dumpInfo();
2866 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -05002867 return string;
2868 }
2869
Brian Osman9a725dd2017-09-20 09:53:22 -04002870 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
2871 GrPixelConfigIsClamped dstIsClamped) override {
Brian Salomon05441c42017-05-15 16:45:49 -04002872 GrColor* color = &fRRects.front().fColor;
Brian Osman9a725dd2017-09-20 09:53:22 -04002873 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
2874 GrProcessorAnalysisCoverage::kSingleChannel, color);
Brian Salomon05441c42017-05-15 16:45:49 -04002875 }
2876
2877 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
2878
bsalomone46f9fe2015-08-18 06:05:14 -07002879private:
Brian Salomon91326c32017-08-09 16:02:19 -04002880 void onPrepareDraws(Target* target) override {
bsalomoncdaa97b2016-03-08 08:30:14 -08002881 SkMatrix localMatrix;
2882 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002883 return;
2884 }
2885
2886 // Setup geometry processor
Brian Salomonea26d6b2018-01-23 20:33:21 +00002887 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, localMatrix));
joshualitt76e7fb62015-02-11 08:52:27 -08002888
Brian Salomon92be2f72018-06-19 14:33:47 -04002889 SkASSERT(sizeof(EllipseVertex) == gp->debugOnly_vertexStride());
joshualitt76e7fb62015-02-11 08:52:27 -08002890
bsalomonb5238a72015-05-05 07:49:49 -07002891 // drop out the middle quad if we're stroked
jvanverthc3d0e422016-08-25 08:12:35 -07002892 int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
Brian Salomond28a79d2017-10-16 13:01:07 -04002893 sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer(
2894 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider());
joshualitt76e7fb62015-02-11 08:52:27 -08002895
Chris Dalton3809bab2017-06-13 10:55:06 -06002896 PatternHelper helper(GrPrimitiveType::kTriangles);
bsalomonb5238a72015-05-05 07:49:49 -07002897 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(
Brian Salomon92be2f72018-06-19 14:33:47 -04002898 helper.init(target, sizeof(EllipseVertex), indexBuffer.get(),
2899 kVertsPerStandardRRect, indicesPerInstance, fRRects.count()));
bsalomonb5238a72015-05-05 07:49:49 -07002900 if (!verts || !indexBuffer) {
joshualitt4b31de82015-03-05 14:33:41 -08002901 SkDebugf("Could not allocate vertices\n");
2902 return;
2903 }
2904
Brian Salomon05441c42017-05-15 16:45:49 -04002905 for (const auto& rrect : fRRects) {
2906 GrColor color = rrect.fColor;
joshualitt76e7fb62015-02-11 08:52:27 -08002907 // Compute the reciprocals of the radii here to save time in the shader
Brian Salomon05441c42017-05-15 16:45:49 -04002908 SkScalar xRadRecip = SkScalarInvert(rrect.fXRadius);
2909 SkScalar yRadRecip = SkScalarInvert(rrect.fYRadius);
2910 SkScalar xInnerRadRecip = SkScalarInvert(rrect.fInnerXRadius);
2911 SkScalar yInnerRadRecip = SkScalarInvert(rrect.fInnerYRadius);
joshualitt76e7fb62015-02-11 08:52:27 -08002912
2913 // Extend the radii out half a pixel to antialias.
Brian Salomon05441c42017-05-15 16:45:49 -04002914 SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf;
2915 SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf;
joshualitt76e7fb62015-02-11 08:52:27 -08002916
Brian Salomon05441c42017-05-15 16:45:49 -04002917 const SkRect& bounds = rrect.fDevBounds;
joshualitt76e7fb62015-02-11 08:52:27 -08002918
Brian Salomon289e3d82016-12-14 15:52:56 -05002919 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius,
2920 bounds.fBottom - yOuterRadius, bounds.fBottom};
2921 SkScalar yOuterOffsets[4] = {yOuterRadius,
2922 SK_ScalarNearlyZero, // we're using inversesqrt() in
2923 // shader, so can't be exactly 0
2924 SK_ScalarNearlyZero, yOuterRadius};
joshualitt76e7fb62015-02-11 08:52:27 -08002925
2926 for (int i = 0; i < 4; ++i) {
2927 verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002928 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002929 verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
2930 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
2931 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
2932 verts++;
2933
2934 verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002935 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002936 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
2937 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
2938 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
2939 verts++;
2940
2941 verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002942 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002943 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
2944 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
2945 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
2946 verts++;
2947
2948 verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
brianosmanbb2ff942016-02-11 14:15:18 -08002949 verts->fColor = color;
joshualitt76e7fb62015-02-11 08:52:27 -08002950 verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
2951 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
2952 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
2953 verts++;
2954 }
2955 }
Brian Salomon05441c42017-05-15 16:45:49 -04002956 helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
joshualitt76e7fb62015-02-11 08:52:27 -08002957 }
2958
Brian Salomon25a88092016-12-01 09:36:50 -05002959 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
Brian Salomon289e3d82016-12-14 15:52:56 -05002960 EllipticalRRectOp* that = t->cast<EllipticalRRectOp>();
bsalomonabd30f52015-08-13 13:34:48 -07002961
Brian Salomon05441c42017-05-15 16:45:49 -04002962 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
joshualitt8cab9a72015-07-16 09:13:50 -07002963 return false;
2964 }
2965
bsalomoncdaa97b2016-03-08 08:30:14 -08002966 if (fStroked != that->fStroked) {
joshualitt76e7fb62015-02-11 08:52:27 -08002967 return false;
2968 }
2969
Brian Salomon05441c42017-05-15 16:45:49 -04002970 if (fHelper.usesLocalCoords() &&
2971 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
joshualitt76e7fb62015-02-11 08:52:27 -08002972 return false;
2973 }
Brian Salomonea26d6b2018-01-23 20:33:21 +00002974
Brian Salomon05441c42017-05-15 16:45:49 -04002975 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
bsalomon88cf17d2016-07-08 06:40:56 -07002976 this->joinBounds(*that);
joshualitt76e7fb62015-02-11 08:52:27 -08002977 return true;
2978 }
2979
Brian Salomon05441c42017-05-15 16:45:49 -04002980 struct RRect {
bsalomon4b4a7cc2016-07-08 04:42:54 -07002981 GrColor fColor;
2982 SkScalar fXRadius;
2983 SkScalar fYRadius;
2984 SkScalar fInnerXRadius;
2985 SkScalar fInnerYRadius;
2986 SkRect fDevBounds;
2987 };
2988
Brian Salomon289e3d82016-12-14 15:52:56 -05002989 SkMatrix fViewMatrixIfUsingLocalCoords;
Brian Salomon05441c42017-05-15 16:45:49 -04002990 Helper fHelper;
2991 bool fStroked;
2992 SkSTArray<1, RRect, true> fRRects;
reed1b55a962015-09-17 20:16:13 -07002993
Brian Salomon05441c42017-05-15 16:45:49 -04002994 typedef GrMeshDrawOp INHERITED;
joshualitt76e7fb62015-02-11 08:52:27 -08002995};
2996
Robert Phillips7c525e62018-06-12 10:11:12 -04002997static std::unique_ptr<GrDrawOp> make_rrect_op(GrContext* context,
2998 GrPaint&& paint,
Brian Salomon05441c42017-05-15 16:45:49 -04002999 const SkMatrix& viewMatrix,
3000 const SkRRect& rrect,
3001 const SkStrokeRec& stroke) {
joshualitt3e708c52015-04-30 13:49:27 -07003002 SkASSERT(viewMatrix.rectStaysRect());
3003 SkASSERT(rrect.isSimple());
3004 SkASSERT(!rrect.isOval());
commit-bot@chromium.orge0a868c2013-11-22 07:02:11 +00003005
Brian Salomon53e4c3c2016-12-21 11:38:53 -05003006 // RRect ops only handle simple, but not too simple, rrects.
3007 // Do any matrix crunching before we reset the draw state for device coords.
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003008 const SkRect& rrectBounds = rrect.getBounds();
joshualittd96a67b2015-05-05 14:09:05 -07003009 SkRect bounds;
3010 viewMatrix.mapRect(&bounds, rrectBounds);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003011
Mike Reed242135a2018-02-22 13:41:39 -05003012 SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
Brian Salomon289e3d82016-12-14 15:52:56 -05003013 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
3014 viewMatrix[SkMatrix::kMSkewY] * radii.fY);
3015 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
3016 viewMatrix[SkMatrix::kMScaleY] * radii.fY);
commit-bot@chromium.org6bb3efc2013-05-16 13:14:46 +00003017
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003018 SkStrokeRec::Style style = stroke.getStyle();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003019
bsalomon4b4a7cc2016-07-08 04:42:54 -07003020 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
3021 SkVector scaledStroke = {-1, -1};
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003022 SkScalar strokeWidth = stroke.getWidth();
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003023
Brian Salomon289e3d82016-12-14 15:52:56 -05003024 bool isStrokeOnly =
3025 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003026 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
3027
jvanverthc3d0e422016-08-25 08:12:35 -07003028 bool isCircular = (xRadius == yRadius);
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003029 if (hasStroke) {
3030 if (SkStrokeRec::kHairline_Style == style) {
3031 scaledStroke.set(1, 1);
3032 } else {
Brian Salomon289e3d82016-12-14 15:52:56 -05003033 scaledStroke.fX = SkScalarAbs(
3034 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
3035 scaledStroke.fY = SkScalarAbs(
3036 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003037 }
3038
jvanverthc3d0e422016-08-25 08:12:35 -07003039 isCircular = isCircular && scaledStroke.fX == scaledStroke.fY;
3040 // for non-circular rrects, if half of strokewidth is greater than radius,
3041 // we don't handle that right now
Brian Salomon289e3d82016-12-14 15:52:56 -05003042 if (!isCircular && (SK_ScalarHalf * scaledStroke.fX > xRadius ||
3043 SK_ScalarHalf * scaledStroke.fY > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07003044 return nullptr;
commit-bot@chromium.org0a09d712014-04-09 21:26:11 +00003045 }
3046 }
3047
3048 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
3049 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
3050 // patch will have fractional coverage. This only matters when the interior is actually filled.
3051 // We could consider falling back to rect rendering here, since a tiny radius is
3052 // indistinguishable from a square corner.
3053 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
halcanary96fcdcc2015-08-27 07:41:13 -07003054 return nullptr;
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003055 }
3056
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003057 // if the corners are circles, use the circle renderer
jvanverthc3d0e422016-08-25 08:12:35 -07003058 if (isCircular) {
Robert Phillips7c525e62018-06-12 10:11:12 -04003059 return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, xRadius,
3060 scaledStroke.fX, isStrokeOnly);
Brian Salomon289e3d82016-12-14 15:52:56 -05003061 // otherwise we use the ellipse renderer
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003062 } else {
Robert Phillips7c525e62018-06-12 10:11:12 -04003063 return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
3064 xRadius, yRadius, scaledStroke, isStrokeOnly);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003065 }
joshualitt3e708c52015-04-30 13:49:27 -07003066}
3067
Robert Phillips7c525e62018-06-12 10:11:12 -04003068std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrContext* context,
3069 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003070 const SkMatrix& viewMatrix,
3071 const SkRRect& rrect,
3072 const SkStrokeRec& stroke,
3073 const GrShaderCaps* shaderCaps) {
robertphillips0cc2f852016-02-24 13:36:56 -08003074 if (rrect.isOval()) {
Robert Phillips7c525e62018-06-12 10:11:12 -04003075 return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(),
3076 GrStyle(stroke, nullptr), shaderCaps);
joshualitt3e708c52015-04-30 13:49:27 -07003077 }
3078
3079 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
robertphillips0cc2f852016-02-24 13:36:56 -08003080 return nullptr;
joshualitt3e708c52015-04-30 13:49:27 -07003081 }
3082
Robert Phillips7c525e62018-06-12 10:11:12 -04003083 return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke);
commit-bot@chromium.orgf2bfd542013-04-25 15:27:00 +00003084}
joshualitt3e708c52015-04-30 13:49:27 -07003085
bsalomon4b4a7cc2016-07-08 04:42:54 -07003086///////////////////////////////////////////////////////////////////////////////
3087
Robert Phillips7c525e62018-06-12 10:11:12 -04003088std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrContext* context,
3089 GrPaint&& paint,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003090 const SkMatrix& viewMatrix,
3091 const SkRect& oval,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003092 const GrStyle& style,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003093 const GrShaderCaps* shaderCaps) {
bsalomon4b4a7cc2016-07-08 04:42:54 -07003094 // we can draw circles
bsalomon4f3a0ca2016-08-22 13:14:26 -07003095 SkScalar width = oval.width();
Jim Van Verthd952a992017-04-20 17:25:26 -04003096 if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
3097 circle_stays_circle(viewMatrix)) {
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003098 auto r = width / 2.f;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003099 SkPoint center = {oval.centerX(), oval.centerY()};
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003100 if (style.hasNonDashPathEffect()) {
3101 return nullptr;
3102 } else if (style.isDashed()) {
3103 if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
3104 style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
3105 return nullptr;
3106 }
3107 auto onInterval = style.dashIntervals()[0];
3108 auto offInterval = style.dashIntervals()[1];
3109 if (offInterval == 0) {
3110 GrStyle strokeStyle(style.strokeRec(), nullptr);
Robert Phillips7c525e62018-06-12 10:11:12 -04003111 return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
3112 strokeStyle, shaderCaps);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003113 } else if (onInterval == 0) {
3114 // There is nothing to draw but we have no way to indicate that here.
3115 return nullptr;
3116 }
3117 auto angularOnInterval = onInterval / r;
3118 auto angularOffInterval = offInterval / r;
3119 auto phaseAngle = style.dashPhase() / r;
3120 // Currently this function doesn't accept ovals with different start angles, though
3121 // it could.
3122 static const SkScalar kStartAngle = 0.f;
Robert Phillips7c525e62018-06-12 10:11:12 -04003123 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003124 style.strokeRec().getWidth(), kStartAngle,
3125 angularOnInterval, angularOffInterval, phaseAngle);
3126 }
Robert Phillips7c525e62018-06-12 10:11:12 -04003127 return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003128 }
3129
3130 if (style.pathEffect()) {
3131 return nullptr;
bsalomon4b4a7cc2016-07-08 04:42:54 -07003132 }
3133
Stan Ilieveb868aa2017-02-21 11:06:16 -05003134 // prefer the device space ellipse op for batchability
bsalomon4b4a7cc2016-07-08 04:42:54 -07003135 if (viewMatrix.rectStaysRect()) {
Robert Phillips7c525e62018-06-12 10:11:12 -04003136 return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
bsalomon4b4a7cc2016-07-08 04:42:54 -07003137 }
3138
Stan Ilieveb868aa2017-02-21 11:06:16 -05003139 // Otherwise, if we have shader derivative support, render as device-independent
3140 if (shaderCaps->shaderDerivativeSupport()) {
Robert Phillips7c525e62018-06-12 10:11:12 -04003141 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
Stan Ilieveb868aa2017-02-21 11:06:16 -05003142 }
3143
bsalomon4b4a7cc2016-07-08 04:42:54 -07003144 return nullptr;
3145}
3146
3147///////////////////////////////////////////////////////////////////////////////
joshualitt3e708c52015-04-30 13:49:27 -07003148
Robert Phillips7c525e62018-06-12 10:11:12 -04003149std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrContext* context,
3150 GrPaint&& paint,
3151 const SkMatrix& viewMatrix,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003152 const SkRect& oval, SkScalar startAngle,
3153 SkScalar sweepAngle, bool useCenter,
3154 const GrStyle& style,
3155 const GrShaderCaps* shaderCaps) {
bsalomon21af9ca2016-08-25 12:29:23 -07003156 SkASSERT(!oval.isEmpty());
3157 SkASSERT(sweepAngle);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003158 SkScalar width = oval.width();
bsalomon21af9ca2016-08-25 12:29:23 -07003159 if (SkScalarAbs(sweepAngle) >= 360.f) {
3160 return nullptr;
3161 }
bsalomon4f3a0ca2016-08-22 13:14:26 -07003162 if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) {
3163 return nullptr;
3164 }
3165 SkPoint center = {oval.centerX(), oval.centerY()};
Brian Salomon289e3d82016-12-14 15:52:56 -05003166 CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle),
3167 useCenter};
Robert Phillips7c525e62018-06-12 10:11:12 -04003168 return CircleOp::Make(context, std::move(paint), viewMatrix,
3169 center, width / 2.f, style, &arcParams);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003170}
3171
3172///////////////////////////////////////////////////////////////////////////////
3173
Hal Canary6f6961e2017-01-31 13:50:44 -05003174#if GR_TEST_UTILS
joshualitt3e708c52015-04-30 13:49:27 -07003175
Brian Salomon05441c42017-05-15 16:45:49 -04003176GR_DRAW_OP_TEST_DEFINE(CircleOp) {
bsalomon4f3a0ca2016-08-22 13:14:26 -07003177 do {
bsalomoncadf75a2016-08-22 14:24:24 -07003178 SkScalar rotate = random->nextSScalar1() * 360.f;
3179 SkScalar translateX = random->nextSScalar1() * 1000.f;
3180 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003181 SkScalar scale;
3182 do {
3183 scale = random->nextSScalar1() * 100.f;
3184 } while (scale == 0);
bsalomoncadf75a2016-08-22 14:24:24 -07003185 SkMatrix viewMatrix;
3186 viewMatrix.setRotate(rotate);
3187 viewMatrix.postTranslate(translateX, translateY);
3188 viewMatrix.postScale(scale, scale);
bsalomon4f3a0ca2016-08-22 13:14:26 -07003189 SkRect circle = GrTest::TestSquare(random);
3190 SkPoint center = {circle.centerX(), circle.centerY()};
3191 SkScalar radius = circle.width() / 2.f;
3192 SkStrokeRec stroke = GrTest::TestStrokeRec(random);
Brian Salomon289e3d82016-12-14 15:52:56 -05003193 CircleOp::ArcParams arcParamsTmp;
3194 const CircleOp::ArcParams* arcParams = nullptr;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003195 if (random->nextBool()) {
3196 arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2;
robertphillips08197b22016-08-23 06:19:15 -07003197 arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f;
3198 arcParamsTmp.fUseCenter = random->nextBool();
bsalomon4f3a0ca2016-08-22 13:14:26 -07003199 arcParams = &arcParamsTmp;
3200 }
Robert Phillips7c525e62018-06-12 10:11:12 -04003201 std::unique_ptr<GrDrawOp> op = CircleOp::Make(context, std::move(paint), viewMatrix,
3202 center, radius,
Brian Salomonea26d6b2018-01-23 20:33:21 +00003203 GrStyle(stroke, nullptr), arcParams);
Brian Salomon289e3d82016-12-14 15:52:56 -05003204 if (op) {
Brian Salomon5ec9def2016-12-20 15:34:05 -05003205 return op;
bsalomon4f3a0ca2016-08-22 13:14:26 -07003206 }
3207 } while (true);
joshualitt3e708c52015-04-30 13:49:27 -07003208}
3209
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003210GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) {
3211 SkScalar rotate = random->nextSScalar1() * 360.f;
3212 SkScalar translateX = random->nextSScalar1() * 1000.f;
3213 SkScalar translateY = random->nextSScalar1() * 1000.f;
Brian Osman4462c042018-06-08 16:35:44 -04003214 SkScalar scale;
3215 do {
3216 scale = random->nextSScalar1() * 100.f;
3217 } while (scale == 0);
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003218 SkMatrix viewMatrix;
3219 viewMatrix.setRotate(rotate);
3220 viewMatrix.postTranslate(translateX, translateY);
3221 viewMatrix.postScale(scale, scale);
3222 SkRect circle = GrTest::TestSquare(random);
3223 SkPoint center = {circle.centerX(), circle.centerY()};
3224 SkScalar radius = circle.width() / 2.f;
3225 SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius);
3226 SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f);
3227 SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f);
3228 SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f);
3229 SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f);
Robert Phillips7c525e62018-06-12 10:11:12 -04003230 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix,
3231 center, radius, strokeWidth,
Brian Salomon62e4f3d2018-04-20 13:54:11 -04003232 startAngle, onAngle, offAngle, phase);
3233}
3234
Brian Salomon05441c42017-05-15 16:45:49 -04003235GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003236 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt6c891102015-05-13 08:51:49 -07003237 SkRect ellipse = GrTest::TestSquare(random);
Robert Phillips7c525e62018-06-12 10:11:12 -04003238 return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
3239 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003240}
3241
Brian Salomon05441c42017-05-15 16:45:49 -04003242GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003243 SkMatrix viewMatrix = GrTest::TestMatrix(random);
joshualitt6c891102015-05-13 08:51:49 -07003244 SkRect ellipse = GrTest::TestSquare(random);
Robert Phillips7c525e62018-06-12 10:11:12 -04003245 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
3246 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003247}
3248
Brian Salomon05441c42017-05-15 16:45:49 -04003249GR_DRAW_OP_TEST_DEFINE(RRectOp) {
joshualitt3e708c52015-04-30 13:49:27 -07003250 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
joshualitt3e708c52015-04-30 13:49:27 -07003251 const SkRRect& rrect = GrTest::TestRRectSimple(random);
Robert Phillips7c525e62018-06-12 10:11:12 -04003252 return make_rrect_op(context, std::move(paint), viewMatrix, rrect,
3253 GrTest::TestStrokeRec(random));
joshualitt3e708c52015-04-30 13:49:27 -07003254}
3255
3256#endif