blob: 98b3b67f37a06f7bdc05a12ccb1ad8669a0d1e1f [file] [log] [blame]
joshualitt9ff64252015-08-10 09:03:51 -07001/*
Michael Ludwig72ab3462018-12-10 12:43:36 -05002 * Copyright 2018 Google Inc.
joshualitt9ff64252015-08-10 09:03:51 -07003 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Michael Ludwig72ab3462018-12-10 12:43:36 -05008#include "GrStrokeRectOp.h"
9
10#include "GrColor.h"
joshualitt9ff64252015-08-10 09:03:51 -070011#include "GrDefaultGeoProcFactory.h"
Michael Ludwig72ab3462018-12-10 12:43:36 -050012#include "GrDrawOpTest.h"
13#include "GrMeshDrawOp.h"
Brian Salomon742e31d2016-12-07 17:06:19 -050014#include "GrOpFlushState.h"
joshualitt9ff64252015-08-10 09:03:51 -070015#include "GrResourceKey.h"
16#include "GrResourceProvider.h"
Brian Salomonbaaf4392017-06-15 09:59:23 -040017#include "GrSimpleMeshDrawOpHelper.h"
Brian Osmancfec9d52018-11-20 11:39:15 -050018#include "GrVertexWriter.h"
Michael Ludwig72ab3462018-12-10 12:43:36 -050019#include "ops/GrFillRectOp.h"
20#include "SkRandom.h"
Hal Canary6f6961e2017-01-31 13:50:44 -050021#include "SkStrokeRec.h"
joshualitt9ff64252015-08-10 09:03:51 -070022
Michael Ludwig72ab3462018-12-10 12:43:36 -050023namespace {
joshualitt9ff64252015-08-10 09:03:51 -070024
bsalomon8b7a9e12016-07-06 13:06:22 -070025// We support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter
Michael Ludwig72ab3462018-12-10 12:43:36 -050026// limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only
27// supported when using an AA stroke.
28inline static bool allowed_stroke(const SkStrokeRec& stroke, GrAA aa, bool* isMiter) {
bsalomon8b7a9e12016-07-06 13:06:22 -070029 SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
30 stroke.getStyle() == SkStrokeRec::kHairline_Style);
31 // For hairlines, make bevel and round joins appear the same as mitered ones.
32 if (!stroke.getWidth()) {
33 *isMiter = true;
34 return true;
35 }
36 if (stroke.getJoin() == SkPaint::kBevel_Join) {
37 *isMiter = false;
Michael Ludwig72ab3462018-12-10 12:43:36 -050038 return aa == GrAA::kYes; // bevel only supported with AA
bsalomon8b7a9e12016-07-06 13:06:22 -070039 }
40 if (stroke.getJoin() == SkPaint::kMiter_Join) {
41 *isMiter = stroke.getMiter() >= SK_ScalarSqrt2;
Michael Ludwig72ab3462018-12-10 12:43:36 -050042 // Supported under non-AA only if it remains mitered
43 return aa == GrAA::kYes || *isMiter;
bsalomon8b7a9e12016-07-06 13:06:22 -070044 }
45 return false;
46}
47
Michael Ludwig72ab3462018-12-10 12:43:36 -050048
49///////////////////////////////////////////////////////////////////////////////////////////////////
50// Non-AA Stroking
51///////////////////////////////////////////////////////////////////////////////////////////////////
52
53/* create a triangle strip that strokes the specified rect. There are 8
54 unique vertices, but we repeat the last 2 to close up. Alternatively we
55 could use an indices array, and then only send 8 verts, but not sure that
56 would be faster.
57 */
58static void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
59 const SkScalar rad = SkScalarHalf(width);
60
61 verts[0].set(rect.fLeft + rad, rect.fTop + rad);
62 verts[1].set(rect.fLeft - rad, rect.fTop - rad);
63 verts[2].set(rect.fRight - rad, rect.fTop + rad);
64 verts[3].set(rect.fRight + rad, rect.fTop - rad);
65 verts[4].set(rect.fRight - rad, rect.fBottom - rad);
66 verts[5].set(rect.fRight + rad, rect.fBottom + rad);
67 verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
68 verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
69 verts[8] = verts[0];
70 verts[9] = verts[1];
71
72 // TODO: we should be catching this higher up the call stack and just draw a single
73 // non-AA rect
74 if (2*rad >= rect.width()) {
75 verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
76 }
77 if (2*rad >= rect.height()) {
78 verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
79 }
80}
81
82class NonAAStrokeRectOp final : public GrMeshDrawOp {
83private:
84 using Helper = GrSimpleMeshDrawOpHelper;
85
86public:
87 DEFINE_OP_CLASS_ID
88
89 const char* name() const override { return "NonAAStrokeRectOp"; }
90
91 void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
92 fHelper.visitProxies(func);
93 }
94
95#ifdef SK_DEBUG
96 SkString dumpInfo() const override {
97 SkString string;
98 string.appendf(
99 "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
100 "StrokeWidth: %.2f\n",
101 fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
102 fStrokeWidth);
103 string += fHelper.dumpInfo();
104 string += INHERITED::dumpInfo();
105 return string;
106 }
107#endif
108
109 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
110 GrPaint&& paint,
111 const SkMatrix& viewMatrix,
112 const SkRect& rect,
113 const SkStrokeRec& stroke,
114 GrAAType aaType) {
115 bool isMiter;
116 if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) {
117 return nullptr;
118 }
119 Helper::Flags flags = Helper::Flags::kNone;
120 // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
121 // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
122 // when MSAA is enabled because it can cause ugly artifacts.
123 if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
124 flags |= Helper::Flags::kSnapVerticesToPixelCenters;
125 }
126 return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags,
127 viewMatrix, rect,
128 stroke, aaType);
129 }
130
131 NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
132 Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect,
133 const SkStrokeRec& stroke, GrAAType aaType)
134 : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) {
135 fColor = color;
136 fViewMatrix = viewMatrix;
137 fRect = rect;
138 // Sort the rect for hairlines
139 fRect.sort();
140 fStrokeWidth = stroke.getWidth();
141
142 SkScalar rad = SkScalarHalf(fStrokeWidth);
143 SkRect bounds = rect;
144 bounds.outset(rad, rad);
145
146 // If our caller snaps to pixel centers then we have to round out the bounds
147 if (flags & Helper::Flags::kSnapVerticesToPixelCenters) {
148 viewMatrix.mapRect(&bounds);
149 // We want to be consistent with how we snap non-aa lines. To match what we do in
150 // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
151 // pixel to force us to pixel centers.
152 bounds.set(SkScalarFloorToScalar(bounds.fLeft),
153 SkScalarFloorToScalar(bounds.fTop),
154 SkScalarFloorToScalar(bounds.fRight),
155 SkScalarFloorToScalar(bounds.fBottom));
156 bounds.offset(0.5f, 0.5f);
157 this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
158 } else {
159 this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
160 }
161 }
162
163 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
164
165 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
166 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone,
167 &fColor);
168 }
169
170private:
171 void onPrepareDraws(Target* target) override {
172 sk_sp<GrGeometryProcessor> gp;
173 {
174 using namespace GrDefaultGeoProcFactory;
175 Color color(fColor);
176 LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
177 ? LocalCoords::kUsePosition_Type
178 : LocalCoords::kUnused_Type;
179 gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color,
180 Coverage::kSolid_Type, localCoordsType,
181 fViewMatrix);
182 }
183
184 size_t kVertexStride = gp->vertexStride();
185 int vertexCount = kVertsPerHairlineRect;
186 if (fStrokeWidth > 0) {
187 vertexCount = kVertsPerStrokeRect;
188 }
189
190 const GrBuffer* vertexBuffer;
191 int firstVertex;
192
193 void* verts =
194 target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
195
196 if (!verts) {
197 SkDebugf("Could not allocate vertices\n");
198 return;
199 }
200
201 SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
202
203 GrPrimitiveType primType;
204 if (fStrokeWidth > 0) {
205 primType = GrPrimitiveType::kTriangleStrip;
206 init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth);
207 } else {
208 // hairline
209 primType = GrPrimitiveType::kLineStrip;
210 vertex[0].set(fRect.fLeft, fRect.fTop);
211 vertex[1].set(fRect.fRight, fRect.fTop);
212 vertex[2].set(fRect.fRight, fRect.fBottom);
213 vertex[3].set(fRect.fLeft, fRect.fBottom);
214 vertex[4].set(fRect.fLeft, fRect.fTop);
215 }
216
217 GrMesh* mesh = target->allocMesh(primType);
218 mesh->setNonIndexedNonInstanced(vertexCount);
219 mesh->setVertexData(vertexBuffer, firstVertex);
220 auto pipe = fHelper.makePipeline(target);
221 target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
222 }
223
224 // TODO: override onCombineIfPossible
225
226 Helper fHelper;
227 SkPMColor4f fColor;
228 SkMatrix fViewMatrix;
229 SkRect fRect;
230 SkScalar fStrokeWidth;
231
232 const static int kVertsPerHairlineRect = 5;
233 const static int kVertsPerStrokeRect = 10;
234
235 typedef GrMeshDrawOp INHERITED;
236};
237
238///////////////////////////////////////////////////////////////////////////////////////////////////
239// AA Stroking
240///////////////////////////////////////////////////////////////////////////////////////////////////
241
242GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
243GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
244
245static void compute_aa_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside,
246 bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect,
247 SkScalar strokeWidth, bool miterStroke) {
bsalomon8b7a9e12016-07-06 13:06:22 -0700248 SkRect devRect;
249 viewMatrix.mapRect(&devRect, rect);
250
251 SkVector devStrokeSize;
252 if (strokeWidth > 0) {
253 devStrokeSize.set(strokeWidth, strokeWidth);
254 viewMatrix.mapVectors(&devStrokeSize, 1);
255 devStrokeSize.setAbs(devStrokeSize);
256 } else {
257 devStrokeSize.set(SK_Scalar1, SK_Scalar1);
258 }
259
260 const SkScalar dx = devStrokeSize.fX;
261 const SkScalar dy = devStrokeSize.fY;
Mike Reed8be952a2017-02-13 20:44:33 -0500262 const SkScalar rx = SkScalarHalf(dx);
263 const SkScalar ry = SkScalarHalf(dy);
bsalomon8b7a9e12016-07-06 13:06:22 -0700264
265 *devOutside = devRect;
266 *devOutsideAssist = devRect;
267 *devInside = devRect;
268
269 devOutside->outset(rx, ry);
270 devInside->inset(rx, ry);
271
272 // If we have a degenerate stroking rect(ie the stroke is larger than inner rect) then we
273 // make a degenerate inside rect to avoid double hitting. We will also jam all of the points
274 // together when we render these rects.
275 SkScalar spare;
276 {
277 SkScalar w = devRect.width() - dx;
278 SkScalar h = devRect.height() - dy;
279 spare = SkTMin(w, h);
280 }
281
282 *isDegenerate = spare <= 0;
283 if (*isDegenerate) {
284 devInside->fLeft = devInside->fRight = devRect.centerX();
285 devInside->fTop = devInside->fBottom = devRect.centerY();
286 }
287
288 // For bevel-stroke, use 2 SkRect instances(devOutside and devOutsideAssist)
289 // to draw the outside of the octagon. Because there are 8 vertices on the outer
290 // edge, while vertex number of inner edge is 4, the same as miter-stroke.
291 if (!miterStroke) {
292 devOutside->inset(0, ry);
293 devOutsideAssist->outset(0, ry);
294 }
295}
296
Michael Ludwig72ab3462018-12-10 12:43:36 -0500297static sk_sp<GrGeometryProcessor> create_aa_stroke_rect_gp(const GrShaderCaps* shaderCaps,
298 bool tweakAlphaForCoverage,
299 const SkMatrix& viewMatrix,
300 bool usesLocalCoords) {
joshualitt9ff64252015-08-10 09:03:51 -0700301 using namespace GrDefaultGeoProcFactory;
302
joshualitt9ff64252015-08-10 09:03:51 -0700303 Coverage::Type coverageType;
Brian Salomon8c5bad32016-12-20 14:43:36 -0500304 if (tweakAlphaForCoverage) {
joshualitt9ff64252015-08-10 09:03:51 -0700305 coverageType = Coverage::kSolid_Type;
306 } else {
307 coverageType = Coverage::kAttribute_Type;
308 }
Brian Salomon8c852be2017-01-04 10:44:42 -0500309 LocalCoords::Type localCoordsType =
310 usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
Ruiqi Maob609e6d2018-07-17 10:19:38 -0400311 return MakeForDeviceSpace(shaderCaps,
312 Color::kPremulGrColorAttribute_Type,
313 coverageType,
314 localCoordsType,
Brian Salomon3de0aee2017-01-29 09:34:17 -0500315 viewMatrix);
joshualitt9ff64252015-08-10 09:03:51 -0700316}
317
Brian Salomonbaaf4392017-06-15 09:59:23 -0400318class AAStrokeRectOp final : public GrMeshDrawOp {
319private:
320 using Helper = GrSimpleMeshDrawOpHelper;
321
joshualitt3566d442015-09-18 07:12:55 -0700322public:
Brian Salomon25a88092016-12-01 09:36:50 -0500323 DEFINE_OP_CLASS_ID
joshualitt3566d442015-09-18 07:12:55 -0700324
Robert Phillips7c525e62018-06-12 10:11:12 -0400325 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
326 GrPaint&& paint,
327 const SkMatrix& viewMatrix,
328 const SkRect& devOutside,
329 const SkRect& devInside) {
330 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix,
331 devOutside, devInside);
Brian Salomonbaaf4392017-06-15 09:59:23 -0400332 }
333
Brian Osmancf860852018-10-31 14:04:39 -0400334 AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -0400335 const SkMatrix& viewMatrix, const SkRect& devOutside, const SkRect& devInside)
Brian Salomonbaaf4392017-06-15 09:59:23 -0400336 : INHERITED(ClassID())
337 , fHelper(helperArgs, GrAAType::kCoverage)
338 , fViewMatrix(viewMatrix) {
caryclarkd6562002016-07-27 12:02:07 -0700339 SkASSERT(!devOutside.isEmpty());
340 SkASSERT(!devInside.isEmpty());
joshualitt3566d442015-09-18 07:12:55 -0700341
Brian Salomon8c5bad32016-12-20 14:43:36 -0500342 fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, false});
bsalomon88cf17d2016-07-08 06:40:56 -0700343 this->setBounds(devOutside, HasAABloat::kYes, IsZeroArea::kNo);
bsalomon8b7a9e12016-07-06 13:06:22 -0700344 fMiterStroke = true;
345 }
346
Robert Phillips7c525e62018-06-12 10:11:12 -0400347 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
348 GrPaint&& paint,
349 const SkMatrix& viewMatrix,
350 const SkRect& rect,
351 const SkStrokeRec& stroke) {
bsalomon8b7a9e12016-07-06 13:06:22 -0700352 bool isMiter;
Michael Ludwig72ab3462018-12-10 12:43:36 -0500353 if (!allowed_stroke(stroke, GrAA::kYes, &isMiter)) {
bsalomon8b7a9e12016-07-06 13:06:22 -0700354 return nullptr;
355 }
Robert Phillips7c525e62018-06-12 10:11:12 -0400356 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, rect,
357 stroke, isMiter);
Brian Salomonbaaf4392017-06-15 09:59:23 -0400358 }
bsalomon8b7a9e12016-07-06 13:06:22 -0700359
Brian Osmancf860852018-10-31 14:04:39 -0400360 AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
Brian Osman936fe7d2018-10-30 15:30:35 -0400361 const SkMatrix& viewMatrix, const SkRect& rect, const SkStrokeRec& stroke,
362 bool isMiter)
Brian Salomonbaaf4392017-06-15 09:59:23 -0400363 : INHERITED(ClassID())
364 , fHelper(helperArgs, GrAAType::kCoverage)
365 , fViewMatrix(viewMatrix) {
366 fMiterStroke = isMiter;
367 RectInfo& info = fRects.push_back();
Michael Ludwig72ab3462018-12-10 12:43:36 -0500368 compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
369 &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
Brian Salomon8c5bad32016-12-20 14:43:36 -0500370 info.fColor = color;
Brian Salomon510dd422017-03-16 12:15:22 -0400371 if (isMiter) {
Brian Salomonbaaf4392017-06-15 09:59:23 -0400372 this->setBounds(info.fDevOutside, HasAABloat::kYes, IsZeroArea::kNo);
Brian Salomon510dd422017-03-16 12:15:22 -0400373 } else {
374 // The outer polygon of the bevel stroke is an octagon specified by the points of a
375 // pair of overlapping rectangles where one is wide and the other is narrow.
376 SkRect bounds = info.fDevOutside;
377 bounds.joinPossiblyEmptyRect(info.fDevOutsideAssist);
Brian Salomonbaaf4392017-06-15 09:59:23 -0400378 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
Brian Salomon510dd422017-03-16 12:15:22 -0400379 }
joshualitt3566d442015-09-18 07:12:55 -0700380 }
381
382 const char* name() const override { return "AAStrokeRect"; }
383
Brian Salomon7d94bb52018-10-12 14:37:19 -0400384 void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
Robert Phillipsb493eeb2017-09-13 13:10:52 -0400385 fHelper.visitProxies(func);
386 }
387
Brian Osman9a390ac2018-11-12 09:47:48 -0500388#ifdef SK_DEBUG
Brian Salomon7c3e7182016-12-01 09:35:30 -0500389 SkString dumpInfo() const override {
390 SkString string;
Brian Salomon8c5bad32016-12-20 14:43:36 -0500391 for (const auto& info : fRects) {
Brian Salomon6a639042016-12-14 11:08:17 -0500392 string.appendf(
393 "Color: 0x%08x, ORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
394 "AssistORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
395 "IRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], Degen: %d",
Brian Osmancf860852018-10-31 14:04:39 -0400396 info.fColor.toBytes_RGBA(), info.fDevOutside.fLeft, info.fDevOutside.fTop,
Brian Salomon8c5bad32016-12-20 14:43:36 -0500397 info.fDevOutside.fRight, info.fDevOutside.fBottom, info.fDevOutsideAssist.fLeft,
398 info.fDevOutsideAssist.fTop, info.fDevOutsideAssist.fRight,
399 info.fDevOutsideAssist.fBottom, info.fDevInside.fLeft, info.fDevInside.fTop,
400 info.fDevInside.fRight, info.fDevInside.fBottom, info.fDegenerate);
Brian Salomon7c3e7182016-12-01 09:35:30 -0500401 }
Brian Salomon82dfd3d2017-06-14 12:30:35 -0400402 string += fHelper.dumpInfo();
403 string += INHERITED::dumpInfo();
Brian Salomon7c3e7182016-12-01 09:35:30 -0500404 return string;
405 }
Brian Osman9a390ac2018-11-12 09:47:48 -0500406#endif
Brian Salomon7c3e7182016-12-01 09:35:30 -0500407
Brian Salomonbaaf4392017-06-15 09:59:23 -0400408 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
Brian Salomona0485d92017-06-14 19:08:01 -0400409
Brian Osman532b3f92018-07-11 10:02:07 -0400410 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
411 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
Brian Salomonbaaf4392017-06-15 09:59:23 -0400412 &fRects.back().fColor);
Brian Salomona0485d92017-06-14 19:08:01 -0400413 }
Brian Salomonbaaf4392017-06-15 09:59:23 -0400414
415private:
Brian Salomon91326c32017-08-09 16:02:19 -0400416 void onPrepareDraws(Target*) override;
joshualittaa37a962015-09-18 13:03:25 -0700417
joshualitt3566d442015-09-18 07:12:55 -0700418 static const int kMiterIndexCnt = 3 * 24;
419 static const int kMiterVertexCnt = 16;
420 static const int kNumMiterRectsInIndexBuffer = 256;
421
422 static const int kBevelIndexCnt = 48 + 36 + 24;
423 static const int kBevelVertexCnt = 24;
424 static const int kNumBevelRectsInIndexBuffer = 256;
425
Brian Salomond28a79d2017-10-16 13:01:07 -0400426 static sk_sp<const GrBuffer> GetIndexBuffer(GrResourceProvider*, bool miterStroke);
joshualitt3566d442015-09-18 07:12:55 -0700427
joshualittaa37a962015-09-18 13:03:25 -0700428 const SkMatrix& viewMatrix() const { return fViewMatrix; }
429 bool miterStroke() const { return fMiterStroke; }
joshualitt3566d442015-09-18 07:12:55 -0700430
Brian Salomon7eae3e02018-08-07 14:02:38 +0000431 CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
joshualitt3566d442015-09-18 07:12:55 -0700432
Brian Osmancfec9d52018-11-20 11:39:15 -0500433 void generateAAStrokeRectGeometry(GrVertexWriter& vertices,
joshualitt3566d442015-09-18 07:12:55 -0700434 GrColor color,
435 const SkRect& devOutside,
436 const SkRect& devOutsideAssist,
437 const SkRect& devInside,
438 bool miterStroke,
joshualitt11edad92015-09-22 10:32:28 -0700439 bool degenerate,
joshualitt3566d442015-09-18 07:12:55 -0700440 bool tweakAlphaForCoverage) const;
441
bsalomon8b7a9e12016-07-06 13:06:22 -0700442 // TODO support AA rotated stroke rects by copying around view matrices
Brian Salomon8c5bad32016-12-20 14:43:36 -0500443 struct RectInfo {
Brian Osmancf860852018-10-31 14:04:39 -0400444 SkPMColor4f fColor;
bsalomon8b7a9e12016-07-06 13:06:22 -0700445 SkRect fDevOutside;
446 SkRect fDevOutsideAssist;
447 SkRect fDevInside;
448 bool fDegenerate;
449 };
450
Brian Salomonbaaf4392017-06-15 09:59:23 -0400451 Helper fHelper;
Brian Salomon8c5bad32016-12-20 14:43:36 -0500452 SkSTArray<1, RectInfo, true> fRects;
joshualittaa37a962015-09-18 13:03:25 -0700453 SkMatrix fViewMatrix;
454 bool fMiterStroke;
joshualitt3566d442015-09-18 07:12:55 -0700455
Brian Salomonbaaf4392017-06-15 09:59:23 -0400456 typedef GrMeshDrawOp INHERITED;
joshualitt3566d442015-09-18 07:12:55 -0700457};
458
Brian Salomon91326c32017-08-09 16:02:19 -0400459void AAStrokeRectOp::onPrepareDraws(Target* target) {
Michael Ludwig72ab3462018-12-10 12:43:36 -0500460 sk_sp<GrGeometryProcessor> gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(),
461 fHelper.compatibleWithAlphaAsCoverage(),
462 this->viewMatrix(),
463 fHelper.usesLocalCoords()));
joshualitt9ff64252015-08-10 09:03:51 -0700464 if (!gp) {
465 SkDebugf("Couldn't create GrGeometryProcessor\n");
466 return;
467 }
468
joshualitt9ff64252015-08-10 09:03:51 -0700469 int innerVertexNum = 4;
470 int outerVertexNum = this->miterStroke() ? 4 : 8;
471 int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2;
472 int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt;
Brian Salomon8c5bad32016-12-20 14:43:36 -0500473 int instanceCount = fRects.count();
joshualitt9ff64252015-08-10 09:03:51 -0700474
Brian Salomon7eae3e02018-08-07 14:02:38 +0000475 sk_sp<const GrBuffer> indexBuffer =
476 GetIndexBuffer(target->resourceProvider(), this->miterStroke());
Brian Osmancfec9d52018-11-20 11:39:15 -0500477 PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(), indexBuffer.get(),
Brian Salomon7eae3e02018-08-07 14:02:38 +0000478 verticesPerInstance, indicesPerInstance, instanceCount);
Brian Osmancfec9d52018-11-20 11:39:15 -0500479 GrVertexWriter vertices{ helper.vertices() };
480 if (!vertices.fPtr || !indexBuffer) {
Brian Salomon6a639042016-12-14 11:08:17 -0500481 SkDebugf("Could not allocate vertices\n");
482 return;
483 }
joshualitt9ff64252015-08-10 09:03:51 -0700484
485 for (int i = 0; i < instanceCount; i++) {
Brian Salomon8c5bad32016-12-20 14:43:36 -0500486 const RectInfo& info = fRects[i];
joshualitt9ff64252015-08-10 09:03:51 -0700487 this->generateAAStrokeRectGeometry(vertices,
Brian Osmancf860852018-10-31 14:04:39 -0400488 info.fColor.toBytes_RGBA(), // TODO4F
Brian Salomon8c5bad32016-12-20 14:43:36 -0500489 info.fDevOutside,
490 info.fDevOutsideAssist,
491 info.fDevInside,
joshualittaa37a962015-09-18 13:03:25 -0700492 fMiterStroke,
Brian Salomon8c5bad32016-12-20 14:43:36 -0500493 info.fDegenerate,
Brian Salomonbaaf4392017-06-15 09:59:23 -0400494 fHelper.compatibleWithAlphaAsCoverage());
joshualitt9ff64252015-08-10 09:03:51 -0700495 }
Brian Salomon49348902018-06-26 09:12:38 -0400496 auto pipe = fHelper.makePipeline(target);
Brian Salomon7eae3e02018-08-07 14:02:38 +0000497 helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
joshualitt9ff64252015-08-10 09:03:51 -0700498}
499
Brian Salomond28a79d2017-10-16 13:01:07 -0400500sk_sp<const GrBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider,
501 bool miterStroke) {
joshualitt9ff64252015-08-10 09:03:51 -0700502 if (miterStroke) {
Brian Salomon6a639042016-12-14 11:08:17 -0500503 // clang-format off
joshualitt9ff64252015-08-10 09:03:51 -0700504 static const uint16_t gMiterIndices[] = {
505 0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0,
506 1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0,
507 2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0,
508 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0,
509
510 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4,
511 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4,
512 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4,
513 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4,
514
515 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8,
516 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8,
517 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8,
518 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8,
519 };
Brian Salomon6a639042016-12-14 11:08:17 -0500520 // clang-format on
joshualitt9ff64252015-08-10 09:03:51 -0700521 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gMiterIndices) == kMiterIndexCnt);
522 GR_DEFINE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
Chris Daltonff926502017-05-03 14:36:54 -0400523 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon6a639042016-12-14 11:08:17 -0500524 gMiterIndices, kMiterIndexCnt, kNumMiterRectsInIndexBuffer, kMiterVertexCnt,
525 gMiterIndexBufferKey);
joshualitt9ff64252015-08-10 09:03:51 -0700526 } else {
527 /**
528 * As in miter-stroke, index = a + b, and a is the current index, b is the shift
529 * from the first index. The index layout:
530 * outer AA line: 0~3, 4~7
531 * outer edge: 8~11, 12~15
532 * inner edge: 16~19
533 * inner AA line: 20~23
534 * Following comes a bevel-stroke rect and its indices:
535 *
536 * 4 7
537 * *********************************
538 * * ______________________________ *
539 * * / 12 15 \ *
540 * * / \ *
541 * 0 * |8 16_____________________19 11 | * 3
542 * * | | | | *
543 * * | | **************** | | *
544 * * | | * 20 23 * | | *
545 * * | | * * | | *
546 * * | | * 21 22 * | | *
547 * * | | **************** | | *
548 * * | |____________________| | *
549 * 1 * |9 17 18 10| * 2
550 * * \ / *
551 * * \13 __________________________14/ *
552 * * *
553 * **********************************
554 * 5 6
555 */
Brian Salomon6a639042016-12-14 11:08:17 -0500556 // clang-format off
joshualitt9ff64252015-08-10 09:03:51 -0700557 static const uint16_t gBevelIndices[] = {
558 // Draw outer AA, from outer AA line to outer edge, shift is 0.
559 0 + 0, 1 + 0, 9 + 0, 9 + 0, 8 + 0, 0 + 0,
560 1 + 0, 5 + 0, 13 + 0, 13 + 0, 9 + 0, 1 + 0,
561 5 + 0, 6 + 0, 14 + 0, 14 + 0, 13 + 0, 5 + 0,
562 6 + 0, 2 + 0, 10 + 0, 10 + 0, 14 + 0, 6 + 0,
563 2 + 0, 3 + 0, 11 + 0, 11 + 0, 10 + 0, 2 + 0,
564 3 + 0, 7 + 0, 15 + 0, 15 + 0, 11 + 0, 3 + 0,
565 7 + 0, 4 + 0, 12 + 0, 12 + 0, 15 + 0, 7 + 0,
566 4 + 0, 0 + 0, 8 + 0, 8 + 0, 12 + 0, 4 + 0,
567
568 // Draw the stroke, from outer edge to inner edge, shift is 8.
569 0 + 8, 1 + 8, 9 + 8, 9 + 8, 8 + 8, 0 + 8,
570 1 + 8, 5 + 8, 9 + 8,
571 5 + 8, 6 + 8, 10 + 8, 10 + 8, 9 + 8, 5 + 8,
572 6 + 8, 2 + 8, 10 + 8,
573 2 + 8, 3 + 8, 11 + 8, 11 + 8, 10 + 8, 2 + 8,
574 3 + 8, 7 + 8, 11 + 8,
575 7 + 8, 4 + 8, 8 + 8, 8 + 8, 11 + 8, 7 + 8,
576 4 + 8, 0 + 8, 8 + 8,
577
578 // Draw the inner AA, from inner edge to inner AA line, shift is 16.
579 0 + 16, 1 + 16, 5 + 16, 5 + 16, 4 + 16, 0 + 16,
580 1 + 16, 2 + 16, 6 + 16, 6 + 16, 5 + 16, 1 + 16,
581 2 + 16, 3 + 16, 7 + 16, 7 + 16, 6 + 16, 2 + 16,
582 3 + 16, 0 + 16, 4 + 16, 4 + 16, 7 + 16, 3 + 16,
583 };
Brian Salomon6a639042016-12-14 11:08:17 -0500584 // clang-format on
joshualitt9ff64252015-08-10 09:03:51 -0700585 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gBevelIndices) == kBevelIndexCnt);
586
587 GR_DEFINE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
Chris Daltonff926502017-05-03 14:36:54 -0400588 return resourceProvider->findOrCreatePatternedIndexBuffer(
Brian Salomon6a639042016-12-14 11:08:17 -0500589 gBevelIndices, kBevelIndexCnt, kNumBevelRectsInIndexBuffer, kBevelVertexCnt,
590 gBevelIndexBufferKey);
joshualitt9ff64252015-08-10 09:03:51 -0700591 }
592}
593
Brian Salomon7eae3e02018-08-07 14:02:38 +0000594GrOp::CombineResult AAStrokeRectOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
Brian Salomon6a639042016-12-14 11:08:17 -0500595 AAStrokeRectOp* that = t->cast<AAStrokeRectOp>();
bsalomonabd30f52015-08-13 13:34:48 -0700596
Brian Salomonbaaf4392017-06-15 09:59:23 -0400597 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +0000598 return CombineResult::kCannotCombine;
joshualitt9ff64252015-08-10 09:03:51 -0700599 }
600
Brian Salomon53e4c3c2016-12-21 11:38:53 -0500601 // TODO combine across miterstroke changes
joshualitt9ff64252015-08-10 09:03:51 -0700602 if (this->miterStroke() != that->miterStroke()) {
Brian Salomon7eae3e02018-08-07 14:02:38 +0000603 return CombineResult::kCannotCombine;
joshualitt9ff64252015-08-10 09:03:51 -0700604 }
605
606 // We apply the viewmatrix to the rect points on the cpu. However, if the pipeline uses
Brian Salomonbaaf4392017-06-15 09:59:23 -0400607 // local coords then we won't be able to combine. TODO: Upload local coords as an attribute.
608 if (fHelper.usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
Brian Salomon7eae3e02018-08-07 14:02:38 +0000609 return CombineResult::kCannotCombine;
joshualitt9ff64252015-08-10 09:03:51 -0700610 }
611
Brian Salomon8c5bad32016-12-20 14:43:36 -0500612 fRects.push_back_n(that->fRects.count(), that->fRects.begin());
Brian Salomon7eae3e02018-08-07 14:02:38 +0000613 return CombineResult::kMerged;
joshualitt9ff64252015-08-10 09:03:51 -0700614}
615
joshualitt11edad92015-09-22 10:32:28 -0700616static void setup_scale(int* scale, SkScalar inset) {
617 if (inset < SK_ScalarHalf) {
618 *scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
619 SkASSERT(*scale >= 0 && *scale <= 255);
620 } else {
621 *scale = 0xff;
622 }
623}
624
Brian Osmancfec9d52018-11-20 11:39:15 -0500625void AAStrokeRectOp::generateAAStrokeRectGeometry(GrVertexWriter& vertices,
Brian Salomon6a639042016-12-14 11:08:17 -0500626 GrColor color,
627 const SkRect& devOutside,
628 const SkRect& devOutsideAssist,
629 const SkRect& devInside,
630 bool miterStroke,
631 bool degenerate,
632 bool tweakAlphaForCoverage) const {
joshualitt9ff64252015-08-10 09:03:51 -0700633 // We create vertices for four nested rectangles. There are two ramps from 0 to full
634 // coverage, one on the exterior of the stroke and the other on the interior.
joshualitt9ff64252015-08-10 09:03:51 -0700635
joshualitt9ff64252015-08-10 09:03:51 -0700636 // TODO: this only really works if the X & Y margins are the same all around
637 // the rect (or if they are all >= 1.0).
joshualitt11edad92015-09-22 10:32:28 -0700638 SkScalar inset;
639 if (!degenerate) {
640 inset = SkMinScalar(SK_Scalar1, devOutside.fRight - devInside.fRight);
641 inset = SkMinScalar(inset, devInside.fLeft - devOutside.fLeft);
642 inset = SkMinScalar(inset, devInside.fTop - devOutside.fTop);
643 if (miterStroke) {
644 inset = SK_ScalarHalf * SkMinScalar(inset, devOutside.fBottom - devInside.fBottom);
645 } else {
Brian Salomon6a639042016-12-14 11:08:17 -0500646 inset = SK_ScalarHalf *
647 SkMinScalar(inset, devOutsideAssist.fBottom - devInside.fBottom);
joshualitt11edad92015-09-22 10:32:28 -0700648 }
649 SkASSERT(inset >= 0);
joshualitt9ff64252015-08-10 09:03:51 -0700650 } else {
joshualitt11edad92015-09-22 10:32:28 -0700651 // TODO use real devRect here
652 inset = SkMinScalar(devOutside.width(), SK_Scalar1);
Brian Salomon6a639042016-12-14 11:08:17 -0500653 inset = SK_ScalarHalf *
654 SkMinScalar(inset, SkTMax(devOutside.height(), devOutsideAssist.height()));
joshualitt9ff64252015-08-10 09:03:51 -0700655 }
joshualitt9ff64252015-08-10 09:03:51 -0700656
Brian Osmancfec9d52018-11-20 11:39:15 -0500657 auto inset_fan = [](const SkRect& r, SkScalar dx, SkScalar dy) {
658 return GrVertexWriter::TriFanFromRect(r.makeInset(dx, dy));
659 };
joshualitt9ff64252015-08-10 09:03:51 -0700660
Brian Osmancfec9d52018-11-20 11:39:15 -0500661 auto maybe_coverage = [tweakAlphaForCoverage](float coverage) {
662 return GrVertexWriter::If(!tweakAlphaForCoverage, coverage);
663 };
664
665 GrColor outerColor = tweakAlphaForCoverage ? 0 : color;
666
667 // Outermost rect
668 vertices.writeQuad(inset_fan(devOutside, -SK_ScalarHalf, -SK_ScalarHalf),
669 outerColor,
670 maybe_coverage(0.0f));
671
672 if (!miterStroke) {
673 // Second outermost
674 vertices.writeQuad(inset_fan(devOutsideAssist, -SK_ScalarHalf, -SK_ScalarHalf),
675 outerColor,
676 maybe_coverage(0.0f));
joshualitt9ff64252015-08-10 09:03:51 -0700677 }
678
679 // scale is the coverage for the the inner two rects.
680 int scale;
joshualitt11edad92015-09-22 10:32:28 -0700681 setup_scale(&scale, inset);
joshualitt9ff64252015-08-10 09:03:51 -0700682
683 float innerCoverage = GrNormalizeByteToFloat(scale);
684 GrColor scaledColor = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
Brian Osmancfec9d52018-11-20 11:39:15 -0500685 GrColor innerColor = tweakAlphaForCoverage ? scaledColor : color;
joshualitt9ff64252015-08-10 09:03:51 -0700686
Brian Osmancfec9d52018-11-20 11:39:15 -0500687 // Inner rect
688 vertices.writeQuad(inset_fan(devOutside, inset, inset),
689 innerColor,
690 maybe_coverage(innerCoverage));
691
692 if (!miterStroke) {
693 // Second inner
694 vertices.writeQuad(inset_fan(devOutsideAssist, inset, inset),
695 innerColor,
696 maybe_coverage(innerCoverage));
joshualitt9ff64252015-08-10 09:03:51 -0700697 }
698
joshualitt11edad92015-09-22 10:32:28 -0700699 if (!degenerate) {
Brian Osmancfec9d52018-11-20 11:39:15 -0500700 vertices.writeQuad(inset_fan(devInside, -inset, -inset),
701 innerColor,
702 maybe_coverage(innerCoverage));
joshualitt11edad92015-09-22 10:32:28 -0700703
Brian Osmancfec9d52018-11-20 11:39:15 -0500704 // The innermost rect has 0 coverage...
705 vertices.writeQuad(inset_fan(devInside, SK_ScalarHalf, SK_ScalarHalf),
706 (GrColor)0,
707 maybe_coverage(0.0f));
708 } else {
709 // When the interior rect has become degenerate we smoosh to a single point
710 SkASSERT(devInside.fLeft == devInside.fRight && devInside.fTop == devInside.fBottom);
711
712 vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside),
713 innerColor,
714 maybe_coverage(innerCoverage));
715
716 // ... unless we are degenerate, in which case we must apply the scaled coverage
717 vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside),
718 innerColor,
719 maybe_coverage(innerCoverage));
joshualitt9ff64252015-08-10 09:03:51 -0700720 }
721}
722
Michael Ludwig72ab3462018-12-10 12:43:36 -0500723} // anonymous namespace
joshualitt3566d442015-09-18 07:12:55 -0700724
Michael Ludwig72ab3462018-12-10 12:43:36 -0500725namespace GrStrokeRectOp {
726
727std::unique_ptr<GrDrawOp> Make(GrContext* context,
728 GrPaint&& paint,
729 GrAAType aaType,
730 const SkMatrix& viewMatrix,
731 const SkRect& rect,
732 const SkStrokeRec& stroke) {
733 if (aaType == GrAAType::kCoverage) {
734 // The AA op only supports axis-aligned rectangles
735 if (!viewMatrix.rectStaysRect()) {
736 return nullptr;
737 }
738 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
739 } else {
740 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
741 }
742}
743
744std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
745 GrPaint&& paint,
746 const SkMatrix& viewMatrix,
747 const SkRect rects[2]) {
Brian Salomonbaaf4392017-06-15 09:59:23 -0400748 SkASSERT(viewMatrix.rectStaysRect());
749 SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty());
750
751 SkRect devOutside, devInside;
752 viewMatrix.mapRect(&devOutside, rects[0]);
753 viewMatrix.mapRect(&devInside, rects[1]);
754 if (devInside.isEmpty()) {
755 if (devOutside.isEmpty()) {
756 return nullptr;
757 }
Michael Ludwig72ab3462018-12-10 12:43:36 -0500758 return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, viewMatrix,
759 rects[0]);
Brian Salomonbaaf4392017-06-15 09:59:23 -0400760 }
761
Robert Phillips7c525e62018-06-12 10:11:12 -0400762 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside, devInside);
joshualittaa37a962015-09-18 13:03:25 -0700763}
764
Michael Ludwig72ab3462018-12-10 12:43:36 -0500765} // namespace GrStrokeRectOp
joshualitt9ff64252015-08-10 09:03:51 -0700766
Hal Canary6f6961e2017-01-31 13:50:44 -0500767#if GR_TEST_UTILS
joshualitt9ff64252015-08-10 09:03:51 -0700768
Brian Salomon5ec9def2016-12-20 15:34:05 -0500769#include "GrDrawOpTest.h"
joshualitt9ff64252015-08-10 09:03:51 -0700770
Michael Ludwig72ab3462018-12-10 12:43:36 -0500771GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
772 SkMatrix viewMatrix = GrTest::TestMatrix(random);
773 SkRect rect = GrTest::TestRect(random);
774 SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
775 SkPaint strokePaint;
776 strokePaint.setStrokeWidth(strokeWidth);
777 strokePaint.setStyle(SkPaint::kStroke_Style);
778 strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
779 SkStrokeRec strokeRec(strokePaint);
780 GrAAType aaType = GrAAType::kNone;
781 if (fsaaType == GrFSAAType::kUnifiedMSAA) {
782 aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
783 }
784 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType);
785}
786
Brian Salomonbaaf4392017-06-15 09:59:23 -0400787GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) {
joshualitt9ff64252015-08-10 09:03:51 -0700788 bool miterStroke = random->nextBool();
789
bsalomon40ef4852016-05-02 13:22:13 -0700790 // Create either a empty rect or a non-empty rect.
Brian Salomon6a639042016-12-14 11:08:17 -0500791 SkRect rect =
792 random->nextBool() ? SkRect::MakeXYWH(10, 10, 50, 40) : SkRect::MakeXYWH(6, 7, 0, 0);
bsalomon40ef4852016-05-02 13:22:13 -0700793 SkScalar minDim = SkMinScalar(rect.width(), rect.height());
794 SkScalar strokeWidth = random->nextUScalar1() * minDim;
joshualitt9ff64252015-08-10 09:03:51 -0700795
bsalomon40ef4852016-05-02 13:22:13 -0700796 SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
797 rec.setStrokeStyle(strokeWidth);
798 rec.setStrokeParams(SkPaint::kButt_Cap,
Brian Salomon6a639042016-12-14 11:08:17 -0500799 miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f);
bsalomon40ef4852016-05-02 13:22:13 -0700800 SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random);
Michael Ludwig72ab3462018-12-10 12:43:36 -0500801 return AAStrokeRectOp::Make(context, std::move(paint), matrix, rect, rec);
joshualitt9ff64252015-08-10 09:03:51 -0700802}
803
804#endif