1 /*
2 * Copyright 2018 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
8 #include "src/gpu/ops/GrStrokeRectOp.h"
9
10 #include "include/core/SkStrokeRec.h"
11 #include "include/private/GrResourceKey.h"
12 #include "include/utils/SkRandom.h"
13 #include "src/gpu/GrCaps.h"
14 #include "src/gpu/GrColor.h"
15 #include "src/gpu/GrDefaultGeoProcFactory.h"
16 #include "src/gpu/GrDrawOpTest.h"
17 #include "src/gpu/GrOpFlushState.h"
18 #include "src/gpu/GrResourceProvider.h"
19 #include "src/gpu/GrVertexWriter.h"
20 #include "src/gpu/ops/GrFillRectOp.h"
21 #include "src/gpu/ops/GrMeshDrawOp.h"
22 #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
23
24 namespace {
25
26 // We support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter
27 // limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only
28 // supported when using an AA stroke.
allowed_stroke(const SkStrokeRec & stroke,GrAA aa,bool * isMiter)29 inline static bool allowed_stroke(const SkStrokeRec& stroke, GrAA aa, bool* isMiter) {
30 SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
31 stroke.getStyle() == SkStrokeRec::kHairline_Style);
32 // For hairlines, make bevel and round joins appear the same as mitered ones.
33 if (!stroke.getWidth()) {
34 *isMiter = true;
35 return true;
36 }
37 if (stroke.getJoin() == SkPaint::kBevel_Join) {
38 *isMiter = false;
39 return aa == GrAA::kYes; // bevel only supported with AA
40 }
41 if (stroke.getJoin() == SkPaint::kMiter_Join) {
42 *isMiter = stroke.getMiter() >= SK_ScalarSqrt2;
43 // Supported under non-AA only if it remains mitered
44 return aa == GrAA::kYes || *isMiter;
45 }
46 return false;
47 }
48
49
50 ///////////////////////////////////////////////////////////////////////////////////////////////////
51 // Non-AA Stroking
52 ///////////////////////////////////////////////////////////////////////////////////////////////////
53
54 /* create a triangle strip that strokes the specified rect. There are 8
55 unique vertices, but we repeat the last 2 to close up. Alternatively we
56 could use an indices array, and then only send 8 verts, but not sure that
57 would be faster.
58 */
init_nonaa_stroke_rect_strip(SkPoint verts[10],const SkRect & rect,SkScalar width)59 static void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
60 const SkScalar rad = SkScalarHalf(width);
61
62 verts[0].set(rect.fLeft + rad, rect.fTop + rad);
63 verts[1].set(rect.fLeft - rad, rect.fTop - rad);
64 verts[2].set(rect.fRight - rad, rect.fTop + rad);
65 verts[3].set(rect.fRight + rad, rect.fTop - rad);
66 verts[4].set(rect.fRight - rad, rect.fBottom - rad);
67 verts[5].set(rect.fRight + rad, rect.fBottom + rad);
68 verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
69 verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
70 verts[8] = verts[0];
71 verts[9] = verts[1];
72
73 // TODO: we should be catching this higher up the call stack and just draw a single
74 // non-AA rect
75 if (2*rad >= rect.width()) {
76 verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
77 }
78 if (2*rad >= rect.height()) {
79 verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
80 }
81 }
82
83 class NonAAStrokeRectOp final : public GrMeshDrawOp {
84 private:
85 using Helper = GrSimpleMeshDrawOpHelper;
86
87 public:
88 DEFINE_OP_CLASS_ID
89
name() const90 const char* name() const override { return "NonAAStrokeRectOp"; }
91
visitProxies(const VisitProxyFunc & func) const92 void visitProxies(const VisitProxyFunc& func) const override {
93 fHelper.visitProxies(func);
94 }
95
96 #ifdef SK_DEBUG
dumpInfo() const97 SkString dumpInfo() const override {
98 SkString string;
99 string.appendf(
100 "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
101 "StrokeWidth: %.2f\n",
102 fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
103 fStrokeWidth);
104 string += fHelper.dumpInfo();
105 string += INHERITED::dumpInfo();
106 return string;
107 }
108 #endif
109
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,GrAAType aaType)110 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
111 GrPaint&& paint,
112 const SkMatrix& viewMatrix,
113 const SkRect& rect,
114 const SkStrokeRec& stroke,
115 GrAAType aaType) {
116 bool isMiter;
117 if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) {
118 return nullptr;
119 }
120 Helper::InputFlags inputFlags = Helper::InputFlags::kNone;
121 // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
122 // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
123 // when MSAA is enabled because it can cause ugly artifacts.
124 if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
125 inputFlags |= Helper::InputFlags::kSnapVerticesToPixelCenters;
126 }
127 return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), inputFlags,
128 viewMatrix, rect,
129 stroke, aaType);
130 }
131
NonAAStrokeRectOp(const Helper::MakeArgs & helperArgs,const SkPMColor4f & color,Helper::InputFlags inputFlags,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,GrAAType aaType)132 NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
133 Helper::InputFlags inputFlags, const SkMatrix& viewMatrix, const SkRect& rect,
134 const SkStrokeRec& stroke, GrAAType aaType)
135 : INHERITED(ClassID()), fHelper(helperArgs, aaType, inputFlags) {
136 fColor = color;
137 fViewMatrix = viewMatrix;
138 fRect = rect;
139 // Sort the rect for hairlines
140 fRect.sort();
141 fStrokeWidth = stroke.getWidth();
142
143 SkScalar rad = SkScalarHalf(fStrokeWidth);
144 SkRect bounds = rect;
145 bounds.outset(rad, rad);
146
147 // If our caller snaps to pixel centers then we have to round out the bounds
148 if (inputFlags & Helper::InputFlags::kSnapVerticesToPixelCenters) {
149 viewMatrix.mapRect(&bounds);
150 // We want to be consistent with how we snap non-aa lines. To match what we do in
151 // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
152 // pixel to force us to pixel centers.
153 bounds.set(SkScalarFloorToScalar(bounds.fLeft),
154 SkScalarFloorToScalar(bounds.fTop),
155 SkScalarFloorToScalar(bounds.fRight),
156 SkScalarFloorToScalar(bounds.fBottom));
157 bounds.offset(0.5f, 0.5f);
158 this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
159 } else {
160 this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
161 }
162 }
163
fixedFunctionFlags() const164 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
165
finalize(const GrCaps & caps,const GrAppliedClip * clip,bool hasMixedSampledCoverage,GrClampType clampType)166 GrProcessorSet::Analysis finalize(
167 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
168 GrClampType clampType) override {
169 // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
170 return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType,
171 GrProcessorAnalysisCoverage::kNone, &fColor, nullptr);
172 }
173
174 private:
onPrepareDraws(Target * target)175 void onPrepareDraws(Target* target) override {
176 sk_sp<GrGeometryProcessor> gp;
177 {
178 using namespace GrDefaultGeoProcFactory;
179 Color color(fColor);
180 LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
181 ? LocalCoords::kUsePosition_Type
182 : LocalCoords::kUnused_Type;
183 gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color,
184 Coverage::kSolid_Type, localCoordsType,
185 fViewMatrix);
186 }
187
188 size_t kVertexStride = gp->vertexStride();
189 int vertexCount = kVertsPerHairlineRect;
190 if (fStrokeWidth > 0) {
191 vertexCount = kVertsPerStrokeRect;
192 }
193
194 sk_sp<const GrBuffer> vertexBuffer;
195 int firstVertex;
196
197 void* verts =
198 target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
199
200 if (!verts) {
201 SkDebugf("Could not allocate vertices\n");
202 return;
203 }
204
205 SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
206
207 GrPrimitiveType primType;
208 if (fStrokeWidth > 0) {
209 primType = GrPrimitiveType::kTriangleStrip;
210 init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth);
211 } else {
212 // hairline
213 primType = GrPrimitiveType::kLineStrip;
214 vertex[0].set(fRect.fLeft, fRect.fTop);
215 vertex[1].set(fRect.fRight, fRect.fTop);
216 vertex[2].set(fRect.fRight, fRect.fBottom);
217 vertex[3].set(fRect.fLeft, fRect.fBottom);
218 vertex[4].set(fRect.fLeft, fRect.fTop);
219 }
220
221 GrMesh* mesh = target->allocMesh(primType);
222 mesh->setNonIndexedNonInstanced(vertexCount);
223 mesh->setVertexData(std::move(vertexBuffer), firstVertex);
224 target->recordDraw(std::move(gp), mesh);
225 }
226
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)227 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
228 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
229 }
230
231 // TODO: override onCombineIfPossible
232
233 Helper fHelper;
234 SkPMColor4f fColor;
235 SkMatrix fViewMatrix;
236 SkRect fRect;
237 SkScalar fStrokeWidth;
238
239 const static int kVertsPerHairlineRect = 5;
240 const static int kVertsPerStrokeRect = 10;
241
242 typedef GrMeshDrawOp INHERITED;
243 };
244
245 ///////////////////////////////////////////////////////////////////////////////////////////////////
246 // AA Stroking
247 ///////////////////////////////////////////////////////////////////////////////////////////////////
248
249 GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
250 GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
251
compute_aa_rects(SkRect * devOutside,SkRect * devOutsideAssist,SkRect * devInside,bool * isDegenerate,const SkMatrix & viewMatrix,const SkRect & rect,SkScalar strokeWidth,bool miterStroke)252 static void compute_aa_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside,
253 bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect,
254 SkScalar strokeWidth, bool miterStroke) {
255 SkRect devRect;
256 viewMatrix.mapRect(&devRect, rect);
257
258 SkVector devStrokeSize;
259 if (strokeWidth > 0) {
260 devStrokeSize.set(strokeWidth, strokeWidth);
261 viewMatrix.mapVectors(&devStrokeSize, 1);
262 devStrokeSize.setAbs(devStrokeSize);
263 } else {
264 devStrokeSize.set(SK_Scalar1, SK_Scalar1);
265 }
266
267 const SkScalar dx = devStrokeSize.fX;
268 const SkScalar dy = devStrokeSize.fY;
269 const SkScalar rx = SkScalarHalf(dx);
270 const SkScalar ry = SkScalarHalf(dy);
271
272 *devOutside = devRect;
273 *devOutsideAssist = devRect;
274 *devInside = devRect;
275
276 devOutside->outset(rx, ry);
277 devInside->inset(rx, ry);
278
279 // If we have a degenerate stroking rect(ie the stroke is larger than inner rect) then we
280 // make a degenerate inside rect to avoid double hitting. We will also jam all of the points
281 // together when we render these rects.
282 SkScalar spare;
283 {
284 SkScalar w = devRect.width() - dx;
285 SkScalar h = devRect.height() - dy;
286 spare = SkTMin(w, h);
287 }
288
289 *isDegenerate = spare <= 0;
290 if (*isDegenerate) {
291 devInside->fLeft = devInside->fRight = devRect.centerX();
292 devInside->fTop = devInside->fBottom = devRect.centerY();
293 }
294
295 // For bevel-stroke, use 2 SkRect instances(devOutside and devOutsideAssist)
296 // to draw the outside of the octagon. Because there are 8 vertices on the outer
297 // edge, while vertex number of inner edge is 4, the same as miter-stroke.
298 if (!miterStroke) {
299 devOutside->inset(0, ry);
300 devOutsideAssist->outset(0, ry);
301 }
302 }
303
create_aa_stroke_rect_gp(const GrShaderCaps * shaderCaps,bool tweakAlphaForCoverage,const SkMatrix & viewMatrix,bool usesLocalCoords,bool wideColor)304 static sk_sp<GrGeometryProcessor> create_aa_stroke_rect_gp(const GrShaderCaps* shaderCaps,
305 bool tweakAlphaForCoverage,
306 const SkMatrix& viewMatrix,
307 bool usesLocalCoords,
308 bool wideColor) {
309 using namespace GrDefaultGeoProcFactory;
310
311 Coverage::Type coverageType =
312 tweakAlphaForCoverage ? Coverage::kSolid_Type : Coverage::kAttribute_Type;
313 LocalCoords::Type localCoordsType =
314 usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
315 Color::Type colorType =
316 wideColor ? Color::kPremulWideColorAttribute_Type: Color::kPremulGrColorAttribute_Type;
317
318 return MakeForDeviceSpace(shaderCaps, colorType, coverageType, localCoordsType, viewMatrix);
319 }
320
321 class AAStrokeRectOp final : public GrMeshDrawOp {
322 private:
323 using Helper = GrSimpleMeshDrawOpHelper;
324
325 public:
326 DEFINE_OP_CLASS_ID
327
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect & devOutside,const SkRect & devInside)328 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
329 GrPaint&& paint,
330 const SkMatrix& viewMatrix,
331 const SkRect& devOutside,
332 const SkRect& devInside) {
333 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix,
334 devOutside, devInside);
335 }
336
AAStrokeRectOp(const Helper::MakeArgs & helperArgs,const SkPMColor4f & color,const SkMatrix & viewMatrix,const SkRect & devOutside,const SkRect & devInside)337 AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
338 const SkMatrix& viewMatrix, const SkRect& devOutside, const SkRect& devInside)
339 : INHERITED(ClassID())
340 , fHelper(helperArgs, GrAAType::kCoverage)
341 , fViewMatrix(viewMatrix) {
342 SkASSERT(!devOutside.isEmpty());
343 SkASSERT(!devInside.isEmpty());
344
345 fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, false});
346 this->setBounds(devOutside, HasAABloat::kYes, IsZeroArea::kNo);
347 fMiterStroke = true;
348 }
349
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke)350 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
351 GrPaint&& paint,
352 const SkMatrix& viewMatrix,
353 const SkRect& rect,
354 const SkStrokeRec& stroke) {
355 bool isMiter;
356 if (!allowed_stroke(stroke, GrAA::kYes, &isMiter)) {
357 return nullptr;
358 }
359 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, rect,
360 stroke, isMiter);
361 }
362
AAStrokeRectOp(const Helper::MakeArgs & helperArgs,const SkPMColor4f & color,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,bool isMiter)363 AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
364 const SkMatrix& viewMatrix, const SkRect& rect, const SkStrokeRec& stroke,
365 bool isMiter)
366 : INHERITED(ClassID())
367 , fHelper(helperArgs, GrAAType::kCoverage)
368 , fViewMatrix(viewMatrix) {
369 fMiterStroke = isMiter;
370 RectInfo& info = fRects.push_back();
371 compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
372 &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
373 info.fColor = color;
374 if (isMiter) {
375 this->setBounds(info.fDevOutside, HasAABloat::kYes, IsZeroArea::kNo);
376 } else {
377 // The outer polygon of the bevel stroke is an octagon specified by the points of a
378 // pair of overlapping rectangles where one is wide and the other is narrow.
379 SkRect bounds = info.fDevOutside;
380 bounds.joinPossiblyEmptyRect(info.fDevOutsideAssist);
381 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
382 }
383 }
384
name() const385 const char* name() const override { return "AAStrokeRect"; }
386
visitProxies(const VisitProxyFunc & func) const387 void visitProxies(const VisitProxyFunc& func) const override {
388 fHelper.visitProxies(func);
389 }
390
391 #ifdef SK_DEBUG
dumpInfo() const392 SkString dumpInfo() const override {
393 SkString string;
394 for (const auto& info : fRects) {
395 string.appendf(
396 "Color: 0x%08x, ORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
397 "AssistORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
398 "IRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], Degen: %d",
399 info.fColor.toBytes_RGBA(), info.fDevOutside.fLeft, info.fDevOutside.fTop,
400 info.fDevOutside.fRight, info.fDevOutside.fBottom, info.fDevOutsideAssist.fLeft,
401 info.fDevOutsideAssist.fTop, info.fDevOutsideAssist.fRight,
402 info.fDevOutsideAssist.fBottom, info.fDevInside.fLeft, info.fDevInside.fTop,
403 info.fDevInside.fRight, info.fDevInside.fBottom, info.fDegenerate);
404 }
405 string += fHelper.dumpInfo();
406 string += INHERITED::dumpInfo();
407 return string;
408 }
409 #endif
410
fixedFunctionFlags() const411 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
412
finalize(const GrCaps & caps,const GrAppliedClip * clip,bool hasMixedSampledCoverage,GrClampType clampType)413 GrProcessorSet::Analysis finalize(
414 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
415 GrClampType clampType) override {
416 return fHelper.finalizeProcessors(
417 caps, clip, hasMixedSampledCoverage, clampType,
418 GrProcessorAnalysisCoverage::kSingleChannel, &fRects.back().fColor, &fWideColor);
419 }
420
421 private:
422 void onPrepareDraws(Target*) override;
423 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
424
425 static const int kMiterIndexCnt = 3 * 24;
426 static const int kMiterVertexCnt = 16;
427 static const int kNumMiterRectsInIndexBuffer = 256;
428
429 static const int kBevelIndexCnt = 48 + 36 + 24;
430 static const int kBevelVertexCnt = 24;
431 static const int kNumBevelRectsInIndexBuffer = 256;
432
433 static sk_sp<const GrGpuBuffer> GetIndexBuffer(GrResourceProvider*, bool miterStroke);
434
viewMatrix() const435 const SkMatrix& viewMatrix() const { return fViewMatrix; }
miterStroke() const436 bool miterStroke() const { return fMiterStroke; }
437
438 CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
439
440 void generateAAStrokeRectGeometry(GrVertexWriter& vertices,
441 const SkPMColor4f& color,
442 bool wideColor,
443 const SkRect& devOutside,
444 const SkRect& devOutsideAssist,
445 const SkRect& devInside,
446 bool miterStroke,
447 bool degenerate,
448 bool tweakAlphaForCoverage) const;
449
450 // TODO support AA rotated stroke rects by copying around view matrices
451 struct RectInfo {
452 SkPMColor4f fColor;
453 SkRect fDevOutside;
454 SkRect fDevOutsideAssist;
455 SkRect fDevInside;
456 bool fDegenerate;
457 };
458
459 Helper fHelper;
460 SkSTArray<1, RectInfo, true> fRects;
461 SkMatrix fViewMatrix;
462 bool fMiterStroke;
463 bool fWideColor;
464
465 typedef GrMeshDrawOp INHERITED;
466 };
467
onPrepareDraws(Target * target)468 void AAStrokeRectOp::onPrepareDraws(Target* target) {
469 sk_sp<GrGeometryProcessor> gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(),
470 fHelper.compatibleWithCoverageAsAlpha(),
471 this->viewMatrix(),
472 fHelper.usesLocalCoords(),
473 fWideColor));
474 if (!gp) {
475 SkDebugf("Couldn't create GrGeometryProcessor\n");
476 return;
477 }
478
479 int innerVertexNum = 4;
480 int outerVertexNum = this->miterStroke() ? 4 : 8;
481 int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2;
482 int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt;
483 int instanceCount = fRects.count();
484
485 sk_sp<const GrGpuBuffer> indexBuffer =
486 GetIndexBuffer(target->resourceProvider(), this->miterStroke());
487 if (!indexBuffer) {
488 SkDebugf("Could not allocate indices\n");
489 return;
490 }
491 PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(),
492 std::move(indexBuffer), verticesPerInstance, indicesPerInstance,
493 instanceCount);
494 GrVertexWriter vertices{ helper.vertices() };
495 if (!vertices.fPtr) {
496 SkDebugf("Could not allocate vertices\n");
497 return;
498 }
499
500 for (int i = 0; i < instanceCount; i++) {
501 const RectInfo& info = fRects[i];
502 this->generateAAStrokeRectGeometry(vertices,
503 info.fColor,
504 fWideColor,
505 info.fDevOutside,
506 info.fDevOutsideAssist,
507 info.fDevInside,
508 fMiterStroke,
509 info.fDegenerate,
510 fHelper.compatibleWithCoverageAsAlpha());
511 }
512 helper.recordDraw(target, std::move(gp));
513 }
514
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)515 void AAStrokeRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
516 fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
517 }
518
GetIndexBuffer(GrResourceProvider * resourceProvider,bool miterStroke)519 sk_sp<const GrGpuBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider,
520 bool miterStroke) {
521 if (miterStroke) {
522 // clang-format off
523 static const uint16_t gMiterIndices[] = {
524 0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0,
525 1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0,
526 2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0,
527 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0,
528
529 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4,
530 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4,
531 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4,
532 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4,
533
534 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8,
535 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8,
536 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8,
537 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8,
538 };
539 // clang-format on
540 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gMiterIndices) == kMiterIndexCnt);
541 GR_DEFINE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
542 return resourceProvider->findOrCreatePatternedIndexBuffer(
543 gMiterIndices, kMiterIndexCnt, kNumMiterRectsInIndexBuffer, kMiterVertexCnt,
544 gMiterIndexBufferKey);
545 } else {
546 /**
547 * As in miter-stroke, index = a + b, and a is the current index, b is the shift
548 * from the first index. The index layout:
549 * outer AA line: 0~3, 4~7
550 * outer edge: 8~11, 12~15
551 * inner edge: 16~19
552 * inner AA line: 20~23
553 * Following comes a bevel-stroke rect and its indices:
554 *
555 * 4 7
556 * *********************************
557 * * ______________________________ *
558 * * / 12 15 \ *
559 * * / \ *
560 * 0 * |8 16_____________________19 11 | * 3
561 * * | | | | *
562 * * | | **************** | | *
563 * * | | * 20 23 * | | *
564 * * | | * * | | *
565 * * | | * 21 22 * | | *
566 * * | | **************** | | *
567 * * | |____________________| | *
568 * 1 * |9 17 18 10| * 2
569 * * \ / *
570 * * \13 __________________________14/ *
571 * * *
572 * **********************************
573 * 5 6
574 */
575 // clang-format off
576 static const uint16_t gBevelIndices[] = {
577 // Draw outer AA, from outer AA line to outer edge, shift is 0.
578 0 + 0, 1 + 0, 9 + 0, 9 + 0, 8 + 0, 0 + 0,
579 1 + 0, 5 + 0, 13 + 0, 13 + 0, 9 + 0, 1 + 0,
580 5 + 0, 6 + 0, 14 + 0, 14 + 0, 13 + 0, 5 + 0,
581 6 + 0, 2 + 0, 10 + 0, 10 + 0, 14 + 0, 6 + 0,
582 2 + 0, 3 + 0, 11 + 0, 11 + 0, 10 + 0, 2 + 0,
583 3 + 0, 7 + 0, 15 + 0, 15 + 0, 11 + 0, 3 + 0,
584 7 + 0, 4 + 0, 12 + 0, 12 + 0, 15 + 0, 7 + 0,
585 4 + 0, 0 + 0, 8 + 0, 8 + 0, 12 + 0, 4 + 0,
586
587 // Draw the stroke, from outer edge to inner edge, shift is 8.
588 0 + 8, 1 + 8, 9 + 8, 9 + 8, 8 + 8, 0 + 8,
589 1 + 8, 5 + 8, 9 + 8,
590 5 + 8, 6 + 8, 10 + 8, 10 + 8, 9 + 8, 5 + 8,
591 6 + 8, 2 + 8, 10 + 8,
592 2 + 8, 3 + 8, 11 + 8, 11 + 8, 10 + 8, 2 + 8,
593 3 + 8, 7 + 8, 11 + 8,
594 7 + 8, 4 + 8, 8 + 8, 8 + 8, 11 + 8, 7 + 8,
595 4 + 8, 0 + 8, 8 + 8,
596
597 // Draw the inner AA, from inner edge to inner AA line, shift is 16.
598 0 + 16, 1 + 16, 5 + 16, 5 + 16, 4 + 16, 0 + 16,
599 1 + 16, 2 + 16, 6 + 16, 6 + 16, 5 + 16, 1 + 16,
600 2 + 16, 3 + 16, 7 + 16, 7 + 16, 6 + 16, 2 + 16,
601 3 + 16, 0 + 16, 4 + 16, 4 + 16, 7 + 16, 3 + 16,
602 };
603 // clang-format on
604 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gBevelIndices) == kBevelIndexCnt);
605
606 GR_DEFINE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
607 return resourceProvider->findOrCreatePatternedIndexBuffer(
608 gBevelIndices, kBevelIndexCnt, kNumBevelRectsInIndexBuffer, kBevelVertexCnt,
609 gBevelIndexBufferKey);
610 }
611 }
612
onCombineIfPossible(GrOp * t,const GrCaps & caps)613 GrOp::CombineResult AAStrokeRectOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
614 AAStrokeRectOp* that = t->cast<AAStrokeRectOp>();
615
616 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
617 return CombineResult::kCannotCombine;
618 }
619
620 // TODO combine across miterstroke changes
621 if (this->miterStroke() != that->miterStroke()) {
622 return CombineResult::kCannotCombine;
623 }
624
625 // We apply the viewmatrix to the rect points on the cpu. However, if the pipeline uses
626 // local coords then we won't be able to combine. TODO: Upload local coords as an attribute.
627 if (fHelper.usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
628 return CombineResult::kCannotCombine;
629 }
630
631 fRects.push_back_n(that->fRects.count(), that->fRects.begin());
632 fWideColor |= that->fWideColor;
633 return CombineResult::kMerged;
634 }
635
setup_scale(int * scale,SkScalar inset)636 static void setup_scale(int* scale, SkScalar inset) {
637 if (inset < SK_ScalarHalf) {
638 *scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
639 SkASSERT(*scale >= 0 && *scale <= 255);
640 } else {
641 *scale = 0xff;
642 }
643 }
644
generateAAStrokeRectGeometry(GrVertexWriter & vertices,const SkPMColor4f & color,bool wideColor,const SkRect & devOutside,const SkRect & devOutsideAssist,const SkRect & devInside,bool miterStroke,bool degenerate,bool tweakAlphaForCoverage) const645 void AAStrokeRectOp::generateAAStrokeRectGeometry(GrVertexWriter& vertices,
646 const SkPMColor4f& color,
647 bool wideColor,
648 const SkRect& devOutside,
649 const SkRect& devOutsideAssist,
650 const SkRect& devInside,
651 bool miterStroke,
652 bool degenerate,
653 bool tweakAlphaForCoverage) const {
654 // We create vertices for four nested rectangles. There are two ramps from 0 to full
655 // coverage, one on the exterior of the stroke and the other on the interior.
656
657 // TODO: this only really works if the X & Y margins are the same all around
658 // the rect (or if they are all >= 1.0).
659 SkScalar inset;
660 if (!degenerate) {
661 inset = SkMinScalar(SK_Scalar1, devOutside.fRight - devInside.fRight);
662 inset = SkMinScalar(inset, devInside.fLeft - devOutside.fLeft);
663 inset = SkMinScalar(inset, devInside.fTop - devOutside.fTop);
664 if (miterStroke) {
665 inset = SK_ScalarHalf * SkMinScalar(inset, devOutside.fBottom - devInside.fBottom);
666 } else {
667 inset = SK_ScalarHalf *
668 SkMinScalar(inset, devOutsideAssist.fBottom - devInside.fBottom);
669 }
670 SkASSERT(inset >= 0);
671 } else {
672 // TODO use real devRect here
673 inset = SkMinScalar(devOutside.width(), SK_Scalar1);
674 inset = SK_ScalarHalf *
675 SkMinScalar(inset, SkTMax(devOutside.height(), devOutsideAssist.height()));
676 }
677
678 auto inset_fan = [](const SkRect& r, SkScalar dx, SkScalar dy) {
679 return GrVertexWriter::TriFanFromRect(r.makeInset(dx, dy));
680 };
681
682 auto maybe_coverage = [tweakAlphaForCoverage](float coverage) {
683 return GrVertexWriter::If(!tweakAlphaForCoverage, coverage);
684 };
685
686 GrVertexColor outerColor(tweakAlphaForCoverage ? SK_PMColor4fTRANSPARENT : color, wideColor);
687
688 // Outermost rect
689 vertices.writeQuad(inset_fan(devOutside, -SK_ScalarHalf, -SK_ScalarHalf),
690 outerColor,
691 maybe_coverage(0.0f));
692
693 if (!miterStroke) {
694 // Second outermost
695 vertices.writeQuad(inset_fan(devOutsideAssist, -SK_ScalarHalf, -SK_ScalarHalf),
696 outerColor,
697 maybe_coverage(0.0f));
698 }
699
700 // scale is the coverage for the the inner two rects.
701 int scale;
702 setup_scale(&scale, inset);
703
704 float innerCoverage = GrNormalizeByteToFloat(scale);
705 SkPMColor4f scaledColor = color * innerCoverage;
706 GrVertexColor innerColor(tweakAlphaForCoverage ? scaledColor : color, wideColor);
707
708 // Inner rect
709 vertices.writeQuad(inset_fan(devOutside, inset, inset),
710 innerColor,
711 maybe_coverage(innerCoverage));
712
713 if (!miterStroke) {
714 // Second inner
715 vertices.writeQuad(inset_fan(devOutsideAssist, inset, inset),
716 innerColor,
717 maybe_coverage(innerCoverage));
718 }
719
720 if (!degenerate) {
721 vertices.writeQuad(inset_fan(devInside, -inset, -inset),
722 innerColor,
723 maybe_coverage(innerCoverage));
724
725 // The innermost rect has 0 coverage...
726 vertices.writeQuad(inset_fan(devInside, SK_ScalarHalf, SK_ScalarHalf),
727 GrVertexColor(SK_PMColor4fTRANSPARENT, wideColor),
728 maybe_coverage(0.0f));
729 } else {
730 // When the interior rect has become degenerate we smoosh to a single point
731 SkASSERT(devInside.fLeft == devInside.fRight && devInside.fTop == devInside.fBottom);
732
733 vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside),
734 innerColor,
735 maybe_coverage(innerCoverage));
736
737 // ... unless we are degenerate, in which case we must apply the scaled coverage
738 vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside),
739 innerColor,
740 maybe_coverage(innerCoverage));
741 }
742 }
743
744 } // anonymous namespace
745
746 namespace GrStrokeRectOp {
747
Make(GrRecordingContext * context,GrPaint && paint,GrAAType aaType,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke)748 std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
749 GrPaint&& paint,
750 GrAAType aaType,
751 const SkMatrix& viewMatrix,
752 const SkRect& rect,
753 const SkStrokeRec& stroke) {
754 if (aaType == GrAAType::kCoverage) {
755 // The AA op only supports axis-aligned rectangles
756 if (!viewMatrix.rectStaysRect()) {
757 return nullptr;
758 }
759 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
760 } else {
761 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
762 }
763 }
764
MakeNested(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect rects[2])765 std::unique_ptr<GrDrawOp> MakeNested(GrRecordingContext* context,
766 GrPaint&& paint,
767 const SkMatrix& viewMatrix,
768 const SkRect rects[2]) {
769 SkASSERT(viewMatrix.rectStaysRect());
770 SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty());
771
772 SkRect devOutside, devInside;
773 viewMatrix.mapRect(&devOutside, rects[0]);
774 viewMatrix.mapRect(&devInside, rects[1]);
775 if (devInside.isEmpty()) {
776 if (devOutside.isEmpty()) {
777 return nullptr;
778 }
779 return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage,
780 GrQuadAAFlags::kAll,
781 GrQuad::MakeFromRect(rects[0], viewMatrix),
782 GrQuad(rects[0]));
783 }
784
785 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside, devInside);
786 }
787
788 } // namespace GrStrokeRectOp
789
790 #if GR_TEST_UTILS
791
792 #include "src/gpu/GrDrawOpTest.h"
793
GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp)794 GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
795 SkMatrix viewMatrix = GrTest::TestMatrix(random);
796 SkRect rect = GrTest::TestRect(random);
797 SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
798 SkPaint strokePaint;
799 strokePaint.setStrokeWidth(strokeWidth);
800 strokePaint.setStyle(SkPaint::kStroke_Style);
801 strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
802 SkStrokeRec strokeRec(strokePaint);
803 GrAAType aaType = GrAAType::kNone;
804 if (numSamples > 1) {
805 aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
806 }
807 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType);
808 }
809
GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp)810 GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) {
811 bool miterStroke = random->nextBool();
812
813 // Create either a empty rect or a non-empty rect.
814 SkRect rect =
815 random->nextBool() ? SkRect::MakeXYWH(10, 10, 50, 40) : SkRect::MakeXYWH(6, 7, 0, 0);
816 SkScalar minDim = SkMinScalar(rect.width(), rect.height());
817 SkScalar strokeWidth = random->nextUScalar1() * minDim;
818
819 SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
820 rec.setStrokeStyle(strokeWidth);
821 rec.setStrokeParams(SkPaint::kButt_Cap,
822 miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f);
823 SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random);
824 return AAStrokeRectOp::Make(context, std::move(paint), matrix, rect, rec);
825 }
826
827 #endif
828