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 #include "src/gpu/ganesh/ops/StrokeRectOp.h"
8
9 #include "include/core/SkMatrix.h"
10 #include "include/core/SkPaint.h"
11 #include "include/core/SkRect.h"
12 #include "include/core/SkRefCnt.h"
13 #include "include/core/SkScalar.h"
14 #include "include/core/SkString.h"
15 #include "include/core/SkStrokeRec.h"
16 #include "include/gpu/ganesh/GrRecordingContext.h"
17 #include "include/private/SkColorData.h"
18 #include "include/private/base/SkAlignedStorage.h"
19 #include "include/private/base/SkAssert.h"
20 #include "include/private/base/SkDebug.h"
21 #include "include/private/base/SkOnce.h"
22 #include "include/private/base/SkPoint_impl.h"
23 #include "include/private/base/SkTArray.h"
24 #include "include/private/gpu/ganesh/GrTypesPriv.h"
25 #include "src/base/SkRandom.h"
26 #include "src/core/SkMatrixPriv.h"
27 #include "src/gpu/BufferWriter.h"
28 #include "src/gpu/ResourceKey.h"
29 #include "src/gpu/ganesh/GrAppliedClip.h"
30 #include "src/gpu/ganesh/GrBuffer.h"
31 #include "src/gpu/ganesh/GrCaps.h"
32 #include "src/gpu/ganesh/GrDefaultGeoProcFactory.h"
33 #include "src/gpu/ganesh/GrDrawOpTest.h"
34 #include "src/gpu/ganesh/GrGeometryProcessor.h"
35 #include "src/gpu/ganesh/GrMeshDrawTarget.h"
36 #include "src/gpu/ganesh/GrOpFlushState.h"
37 #include "src/gpu/ganesh/GrPaint.h"
38 #include "src/gpu/ganesh/GrProcessorAnalysis.h"
39 #include "src/gpu/ganesh/GrProcessorSet.h"
40 #include "src/gpu/ganesh/GrProgramInfo.h"
41 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
42 #include "src/gpu/ganesh/GrResourceProvider.h"
43 #include "src/gpu/ganesh/GrSimpleMesh.h"
44 #include "src/gpu/ganesh/GrTestUtils.h"
45 #include "src/gpu/ganesh/geometry/GrQuad.h"
46 #include "src/gpu/ganesh/ops/FillRectOp.h"
47 #include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
48 #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h"
49
50 #include <algorithm>
51 #include <array>
52 #include <cstddef>
53 #include <cstdint>
54 #include <utility>
55
56 class GrDstProxyView;
57 class GrGpuBuffer;
58 class GrSurfaceProxyView;
59 class SkArenaAlloc;
60 enum class GrXferBarrierFlags;
61
62 namespace skgpu::ganesh {
63 class SurfaceDrawContext;
64 }
65
66 using namespace skia_private;
67
68 namespace skgpu::ganesh::StrokeRectOp {
69
70 namespace {
71
72 // This emits line primitives for hairlines, so only support hairlines if allowed by caps. Otherwise
73 // we support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter
74 // limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only
75 // supported when using an AA stroke.
allowed_stroke(const GrCaps * caps,const SkStrokeRec & stroke,GrAA aa,bool * isMiter)76 inline bool allowed_stroke(const GrCaps* caps, const SkStrokeRec& stroke, GrAA aa, bool* isMiter) {
77 SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
78 stroke.getStyle() == SkStrokeRec::kHairline_Style);
79 if (caps->avoidLineDraws() && stroke.isHairlineStyle()) {
80 return false;
81 }
82 // For hairlines, make bevel and round joins appear the same as mitered ones.
83 if (!stroke.getWidth()) {
84 *isMiter = true;
85 return true;
86 }
87 if (stroke.getJoin() == SkPaint::kBevel_Join) {
88 *isMiter = false;
89 return aa == GrAA::kYes; // bevel only supported with AA
90 }
91 if (stroke.getJoin() == SkPaint::kMiter_Join) {
92 *isMiter = stroke.getMiter() >= SK_ScalarSqrt2;
93 // Supported under non-AA only if it remains mitered
94 return aa == GrAA::kYes || *isMiter;
95 }
96 return false;
97 }
98
99
100 ///////////////////////////////////////////////////////////////////////////////////////////////////
101 // Non-AA Stroking
102 ///////////////////////////////////////////////////////////////////////////////////////////////////
103
104 /* create a triangle strip that strokes the specified rect. There are 8
105 unique vertices, but we repeat the last 2 to close up. Alternatively we
106 could use an indices array, and then only send 8 verts, but not sure that
107 would be faster.
108 */
init_nonaa_stroke_rect_strip(SkPoint verts[10],const SkRect & rect,SkScalar width)109 void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
110 const SkScalar rad = SkScalarHalf(width);
111
112 verts[0].set(rect.fLeft + rad, rect.fTop + rad);
113 verts[1].set(rect.fLeft - rad, rect.fTop - rad);
114 verts[2].set(rect.fRight - rad, rect.fTop + rad);
115 verts[3].set(rect.fRight + rad, rect.fTop - rad);
116 verts[4].set(rect.fRight - rad, rect.fBottom - rad);
117 verts[5].set(rect.fRight + rad, rect.fBottom + rad);
118 verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
119 verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
120 verts[8] = verts[0];
121 verts[9] = verts[1];
122
123 // TODO: we should be catching this higher up the call stack and just draw a single
124 // non-AA rect
125 if (2*rad >= rect.width()) {
126 verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
127 }
128 if (2*rad >= rect.height()) {
129 verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
130 }
131 }
132
133 class NonAAStrokeRectOp final : public GrMeshDrawOp {
134 private:
135 using Helper = GrSimpleMeshDrawOpHelper;
136
137 public:
138 DEFINE_OP_CLASS_ID
139
name() const140 const char* name() const override { return "NonAAStrokeRectOp"; }
141
visitProxies(const GrVisitProxyFunc & func) const142 void visitProxies(const GrVisitProxyFunc& func) const override {
143 if (fProgramInfo) {
144 fProgramInfo->visitFPProxies(func);
145 } else {
146 fHelper.visitProxies(func);
147 }
148 }
149
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,GrAAType aaType)150 static GrOp::Owner Make(GrRecordingContext* context,
151 GrPaint&& paint,
152 const SkMatrix& viewMatrix,
153 const SkRect& rect,
154 const SkStrokeRec& stroke,
155 GrAAType aaType) {
156 bool isMiter;
157 if (!allowed_stroke(context->priv().caps(), stroke, GrAA::kNo, &isMiter)) {
158 return nullptr;
159 }
160 Helper::InputFlags inputFlags = Helper::InputFlags::kNone;
161 // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
162 // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
163 // when MSAA is enabled because it can cause ugly artifacts.
164 if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
165 inputFlags |= Helper::InputFlags::kSnapVerticesToPixelCenters;
166 }
167 return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), inputFlags,
168 viewMatrix, rect,
169 stroke, aaType);
170 }
171
NonAAStrokeRectOp(GrProcessorSet * processorSet,const SkPMColor4f & color,Helper::InputFlags inputFlags,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,GrAAType aaType)172 NonAAStrokeRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
173 Helper::InputFlags inputFlags, const SkMatrix& viewMatrix, const SkRect& rect,
174 const SkStrokeRec& stroke, GrAAType aaType)
175 : INHERITED(ClassID())
176 , fHelper(processorSet, aaType, inputFlags) {
177 fColor = color;
178 fViewMatrix = viewMatrix;
179 fRect = rect;
180 // Sort the rect for hairlines
181 fRect.sort();
182 fStrokeWidth = stroke.getWidth();
183
184 SkScalar rad = SkScalarHalf(fStrokeWidth);
185 SkRect bounds = rect;
186 bounds.outset(rad, rad);
187
188 // If our caller snaps to pixel centers then we have to round out the bounds
189 if (inputFlags & Helper::InputFlags::kSnapVerticesToPixelCenters) {
190 SkASSERT(!fStrokeWidth || aaType == GrAAType::kNone);
191 viewMatrix.mapRect(&bounds);
192 // We want to be consistent with how we snap non-aa lines. To match what we do in
193 // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
194 // pixel to force us to pixel centers.
195 bounds.setLTRB(SkScalarFloorToScalar(bounds.fLeft),
196 SkScalarFloorToScalar(bounds.fTop),
197 SkScalarFloorToScalar(bounds.fRight),
198 SkScalarFloorToScalar(bounds.fBottom));
199 bounds.offset(0.5f, 0.5f);
200 this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo);
201 } else {
202 HasAABloat aaBloat = (aaType == GrAAType::kNone) ? HasAABloat ::kNo : HasAABloat::kYes;
203 this->setTransformedBounds(bounds, fViewMatrix, aaBloat,
204 fStrokeWidth ? IsHairline::kNo : IsHairline::kYes);
205 }
206 }
207
fixedFunctionFlags() const208 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
209
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)210 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
211 GrClampType clampType) override {
212 // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
213 return fHelper.finalizeProcessors(caps, clip, clampType, GrProcessorAnalysisCoverage::kNone,
214 &fColor, nullptr);
215 }
216
217 private:
programInfo()218 GrProgramInfo* programInfo() override { return fProgramInfo; }
219
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && clip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)220 void onCreateProgramInfo(const GrCaps* caps,
221 SkArenaAlloc* arena,
222 const GrSurfaceProxyView& writeView,
223 bool usesMSAASurface,
224 GrAppliedClip&& clip,
225 const GrDstProxyView& dstProxyView,
226 GrXferBarrierFlags renderPassXferBarriers,
227 GrLoadOp colorLoadOp) override {
228 GrGeometryProcessor* gp;
229 {
230 using namespace GrDefaultGeoProcFactory;
231 Color color(fColor);
232 LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
233 ? LocalCoords::kUsePosition_Type
234 : LocalCoords::kUnused_Type;
235 gp = GrDefaultGeoProcFactory::Make(arena, color, Coverage::kSolid_Type, localCoordsType,
236 fViewMatrix);
237 }
238
239 GrPrimitiveType primType = (fStrokeWidth > 0) ? GrPrimitiveType::kTriangleStrip
240 : GrPrimitiveType::kLineStrip;
241
242 fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface,
243 std::move(clip), dstProxyView, gp, primType,
244 renderPassXferBarriers, colorLoadOp);
245 }
246
onPrepareDraws(GrMeshDrawTarget * target)247 void onPrepareDraws(GrMeshDrawTarget* target) override {
248 if (!fProgramInfo) {
249 this->createProgramInfo(target);
250 }
251
252 size_t kVertexStride = fProgramInfo->geomProc().vertexStride();
253 int vertexCount = kVertsPerHairlineRect;
254 if (fStrokeWidth > 0) {
255 vertexCount = kVertsPerStrokeRect;
256 }
257
258 sk_sp<const GrBuffer> vertexBuffer;
259 int firstVertex;
260
261 void* verts =
262 target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
263
264 if (!verts) {
265 SkDebugf("Could not allocate vertices\n");
266 return;
267 }
268
269 SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
270
271 if (fStrokeWidth > 0) {
272 init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth);
273 } else {
274 // hairline
275 vertex[0].set(fRect.fLeft, fRect.fTop);
276 vertex[1].set(fRect.fRight, fRect.fTop);
277 vertex[2].set(fRect.fRight, fRect.fBottom);
278 vertex[3].set(fRect.fLeft, fRect.fBottom);
279 vertex[4].set(fRect.fLeft, fRect.fTop);
280 }
281
282 fMesh = target->allocMesh();
283 fMesh->set(std::move(vertexBuffer), vertexCount, firstVertex);
284 }
285
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)286 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
287 if (!fMesh) {
288 return;
289 }
290
291 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
292 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
293 flushState->drawMesh(*fMesh);
294 }
295
296 #if defined(GPU_TEST_UTILS)
onDumpInfo() const297 SkString onDumpInfo() const override {
298 return SkStringPrintf("Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
299 "StrokeWidth: %.2f\n%s",
300 fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight,
301 fRect.fBottom, fStrokeWidth, fHelper.dumpInfo().c_str());
302 }
303 #endif
304
305 // TODO: override onCombineIfPossible
306
307 Helper fHelper;
308 SkPMColor4f fColor;
309 SkMatrix fViewMatrix;
310 SkRect fRect;
311 SkScalar fStrokeWidth;
312 GrSimpleMesh* fMesh = nullptr;
313 GrProgramInfo* fProgramInfo = nullptr;
314
315 const static int kVertsPerHairlineRect = 5;
316 const static int kVertsPerStrokeRect = 10;
317
318 using INHERITED = GrMeshDrawOp;
319 };
320
321 ///////////////////////////////////////////////////////////////////////////////////////////////////
322 // AA Stroking
323 ///////////////////////////////////////////////////////////////////////////////////////////////////
324
325 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
326 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
327
stroke_dev_half_size_supported(SkVector devHalfStrokeSize)328 bool stroke_dev_half_size_supported(SkVector devHalfStrokeSize) {
329 // Since the horizontal and vertical strokes share internal corners, the coverage value at that
330 // corner needs to be equal for the horizontal and vertical strokes both.
331 //
332 // The inner coverage values will be equal if the horizontal and vertical stroke widths are
333 // equal (in which case innerCoverage is same for all sides of the rects) or if the horizontal
334 // and vertical stroke widths are both greater than 1 (in which case innerCoverage will always
335 // be 1). In actuality we allow them to be nearly-equal since differing by < 1/1000 will not be
336 // visually detectable when the shape is already less than 1px in thickness.
337 return SkScalarNearlyEqual(devHalfStrokeSize.fX, devHalfStrokeSize.fY) ||
338 std::min(devHalfStrokeSize.fX, devHalfStrokeSize.fY) >= .5f;
339 }
340
compute_aa_rects(const GrCaps & caps,SkRect * devOutside,SkRect * devOutsideAssist,SkRect * devInside,bool * isDegenerate,const SkMatrix & viewMatrix,const SkRect & rect,SkScalar strokeWidth,bool miterStroke,SkVector * devHalfStrokeSize)341 bool compute_aa_rects(const GrCaps& caps,
342 SkRect* devOutside,
343 SkRect* devOutsideAssist,
344 SkRect* devInside,
345 bool* isDegenerate,
346 const SkMatrix& viewMatrix,
347 const SkRect& rect,
348 SkScalar strokeWidth,
349 bool miterStroke,
350 SkVector* devHalfStrokeSize) {
351 SkVector devStrokeSize;
352 if (strokeWidth > 0) {
353 devStrokeSize.set(strokeWidth, strokeWidth);
354 viewMatrix.mapVectors(&devStrokeSize, 1);
355 devStrokeSize.setAbs(devStrokeSize);
356 } else {
357 devStrokeSize.set(SK_Scalar1, SK_Scalar1);
358 }
359
360 const SkScalar dx = devStrokeSize.fX;
361 const SkScalar dy = devStrokeSize.fY;
362 const SkScalar rx = SkScalarHalf(dx);
363 const SkScalar ry = SkScalarHalf(dy);
364
365 devHalfStrokeSize->fX = rx;
366 devHalfStrokeSize->fY = ry;
367
368 SkRect devRect;
369 viewMatrix.mapRect(&devRect, rect);
370
371 // Clip our draw rect 1 full stroke width plus bloat outside the viewport. This avoids
372 // interpolation precision issues with very large coordinates.
373 const float m = caps.maxRenderTargetSize();
374 const SkRect visibilityBounds = SkRect::MakeWH(m, m).makeOutset(dx + 1, dy + 1);
375 if (!devRect.intersect(visibilityBounds)) {
376 return false;
377 }
378
379 *devOutside = devRect;
380 *devOutsideAssist = devRect;
381 *devInside = devRect;
382
383 devOutside->outset(rx, ry);
384 devInside->inset(rx, ry);
385
386 // If we have a degenerate stroking rect(ie the stroke is larger than inner rect) then we
387 // make a degenerate inside rect to avoid double hitting. We will also jam all of the points
388 // together when we render these rects.
389 SkScalar spare;
390 {
391 SkScalar w = devRect.width() - dx;
392 SkScalar h = devRect.height() - dy;
393 spare = std::min(w, h);
394 }
395
396 *isDegenerate = spare <= 0;
397 if (*isDegenerate) {
398 devInside->fLeft = devInside->fRight = devRect.centerX();
399 devInside->fTop = devInside->fBottom = devRect.centerY();
400 }
401
402 // For bevel-stroke, use 2 SkRect instances(devOutside and devOutsideAssist)
403 // to draw the outside of the octagon. Because there are 8 vertices on the outer
404 // edge, while vertex number of inner edge is 4, the same as miter-stroke.
405 if (!miterStroke) {
406 devOutside->inset(0, ry);
407 devOutsideAssist->outset(0, ry);
408 }
409
410 return true;
411 }
412
create_aa_stroke_rect_gp(SkArenaAlloc * arena,bool usesMSAASurface,bool tweakAlphaForCoverage,const SkMatrix & viewMatrix,bool usesLocalCoords,bool wideColor)413 GrGeometryProcessor* create_aa_stroke_rect_gp(SkArenaAlloc* arena,
414 bool usesMSAASurface,
415 bool tweakAlphaForCoverage,
416 const SkMatrix& viewMatrix,
417 bool usesLocalCoords,
418 bool wideColor) {
419 using namespace GrDefaultGeoProcFactory;
420
421 // When MSAA is enabled, we have to extend our AA bloats and interpolate coverage values outside
422 // 0..1. We tell the gp in this case that coverage is an unclamped attribute so it will call
423 // saturate(coverage) in the fragment shader.
424 Coverage::Type coverageType = usesMSAASurface ? Coverage::kAttributeUnclamped_Type
425 : (!tweakAlphaForCoverage ? Coverage::kAttribute_Type
426 : Coverage::kSolid_Type);
427 LocalCoords::Type localCoordsType =
428 usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
429 Color::Type colorType =
430 wideColor ? Color::kPremulWideColorAttribute_Type: Color::kPremulGrColorAttribute_Type;
431
432 return MakeForDeviceSpace(arena, colorType, coverageType, localCoordsType, viewMatrix);
433 }
434
435 class AAStrokeRectOp final : public GrMeshDrawOp {
436 private:
437 using Helper = GrSimpleMeshDrawOpHelper;
438
439 public:
440 DEFINE_OP_CLASS_ID
441
442 // TODO support AA rotated stroke rects by copying around view matrices
443 struct RectInfo {
444 SkPMColor4f fColor;
445 SkRect fDevOutside;
446 SkRect fDevOutsideAssist;
447 SkRect fDevInside;
448 SkVector fDevHalfStrokeSize;
449 bool fDegenerate;
450 };
451
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect & devOutside,const SkRect & devInside,const SkVector & devHalfStrokeSize)452 static GrOp::Owner Make(GrRecordingContext* context,
453 GrPaint&& paint,
454 const SkMatrix& viewMatrix,
455 const SkRect& devOutside,
456 const SkRect& devInside,
457 const SkVector& devHalfStrokeSize) {
458 if (!viewMatrix.rectStaysRect()) {
459 // The AA op only supports axis-aligned rectangles
460 return nullptr;
461 }
462 if (!stroke_dev_half_size_supported(devHalfStrokeSize)) {
463 return nullptr;
464 }
465 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix,
466 devOutside, devInside, devHalfStrokeSize);
467 }
468
AAStrokeRectOp(GrProcessorSet * processorSet,const SkPMColor4f & color,const SkMatrix & viewMatrix,const SkRect & devOutside,const SkRect & devInside,const SkVector & devHalfStrokeSize)469 AAStrokeRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
470 const SkMatrix& viewMatrix, const SkRect& devOutside, const SkRect& devInside,
471 const SkVector& devHalfStrokeSize)
472 : INHERITED(ClassID())
473 , fHelper(processorSet, GrAAType::kCoverage)
474 , fViewMatrix(viewMatrix) {
475 SkASSERT(!devOutside.isEmpty());
476 SkASSERT(!devInside.isEmpty());
477
478 fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, devHalfStrokeSize, false});
479 this->setBounds(devOutside, HasAABloat::kYes, IsHairline::kNo);
480 fMiterStroke = true;
481 }
482
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke)483 static GrOp::Owner Make(GrRecordingContext* context,
484 GrPaint&& paint,
485 const SkMatrix& viewMatrix,
486 const SkRect& rect,
487 const SkStrokeRec& stroke) {
488 if (!viewMatrix.rectStaysRect()) {
489 // The AA op only supports axis-aligned rectangles
490 return nullptr;
491 }
492 bool isMiter;
493 if (!allowed_stroke(context->priv().caps(), stroke, GrAA::kYes, &isMiter)) {
494 return nullptr;
495 }
496 RectInfo info;
497 if (!compute_aa_rects(*context->priv().caps(),
498 &info.fDevOutside,
499 &info.fDevOutsideAssist,
500 &info.fDevInside,
501 &info.fDegenerate,
502 viewMatrix,
503 rect,
504 stroke.getWidth(),
505 isMiter,
506 &info.fDevHalfStrokeSize)) {
507 return nullptr;
508 }
509 if (!stroke_dev_half_size_supported(info.fDevHalfStrokeSize)) {
510 return nullptr;
511 }
512 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, info,
513 isMiter);
514 }
515
AAStrokeRectOp(GrProcessorSet * processorSet,const SkPMColor4f & color,const SkMatrix & viewMatrix,const RectInfo & infoExceptColor,bool isMiter)516 AAStrokeRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
517 const SkMatrix& viewMatrix, const RectInfo& infoExceptColor, bool isMiter)
518 : INHERITED(ClassID())
519 , fHelper(processorSet, GrAAType::kCoverage)
520 , fViewMatrix(viewMatrix) {
521 fMiterStroke = isMiter;
522 RectInfo& info = fRects.push_back(infoExceptColor);
523 info.fColor = color;
524 if (isMiter) {
525 this->setBounds(info.fDevOutside, HasAABloat::kYes, IsHairline::kNo);
526 } else {
527 // The outer polygon of the bevel stroke is an octagon specified by the points of a
528 // pair of overlapping rectangles where one is wide and the other is narrow.
529 SkRect bounds = info.fDevOutside;
530 bounds.joinPossiblyEmptyRect(info.fDevOutsideAssist);
531 this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo);
532 }
533 }
534
name() const535 const char* name() const override { return "AAStrokeRect"; }
536
visitProxies(const GrVisitProxyFunc & func) const537 void visitProxies(const GrVisitProxyFunc& func) const override {
538 if (fProgramInfo) {
539 fProgramInfo->visitFPProxies(func);
540 } else {
541 fHelper.visitProxies(func);
542 }
543 }
544
fixedFunctionFlags() const545 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
546
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)547 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
548 GrClampType clampType) override {
549 return fHelper.finalizeProcessors(caps, clip, clampType,
550 GrProcessorAnalysisCoverage::kSingleChannel,
551 &fRects.back().fColor, &fWideColor);
552 }
553
554 private:
programInfo()555 GrProgramInfo* programInfo() override { return fProgramInfo; }
556
compatibleWithCoverageAsAlpha(bool usesMSAASurface) const557 bool compatibleWithCoverageAsAlpha(bool usesMSAASurface) const {
558 // When MSAA is enabled, we have to extend our AA bloats and interpolate coverage values
559 // outside 0..1. This makes us incompatible with coverage as alpha.
560 return !usesMSAASurface && fHelper.compatibleWithCoverageAsAlpha();
561 }
562
563 void onCreateProgramInfo(const GrCaps*,
564 SkArenaAlloc*,
565 const GrSurfaceProxyView& writeView,
566 bool usesMSAASurface,
567 GrAppliedClip&&,
568 const GrDstProxyView&,
569 GrXferBarrierFlags renderPassXferBarriers,
570 GrLoadOp colorLoadOp) override;
571
572 void onPrepareDraws(GrMeshDrawTarget*) override;
573 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
574
575 #if defined(GPU_TEST_UTILS)
onDumpInfo() const576 SkString onDumpInfo() const override {
577 SkString string;
578 for (const auto& info : fRects) {
579 string.appendf(
580 "Color: 0x%08x, ORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
581 "AssistORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
582 "IRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], Degen: %d",
583 info.fColor.toBytes_RGBA(), info.fDevOutside.fLeft, info.fDevOutside.fTop,
584 info.fDevOutside.fRight, info.fDevOutside.fBottom, info.fDevOutsideAssist.fLeft,
585 info.fDevOutsideAssist.fTop, info.fDevOutsideAssist.fRight,
586 info.fDevOutsideAssist.fBottom, info.fDevInside.fLeft, info.fDevInside.fTop,
587 info.fDevInside.fRight, info.fDevInside.fBottom, info.fDegenerate);
588 }
589 string += fHelper.dumpInfo();
590 return string;
591 }
592 #endif
593
594 static const int kMiterIndexCnt = 3 * 24;
595 static const int kMiterVertexCnt = 16;
596 static const int kNumMiterRectsInIndexBuffer = 256;
597
598 static const int kBevelIndexCnt = 48 + 36 + 24;
599 static const int kBevelVertexCnt = 24;
600 static const int kNumBevelRectsInIndexBuffer = 256;
601
602 static sk_sp<const GrGpuBuffer> GetIndexBuffer(GrResourceProvider*, bool miterStroke);
603
viewMatrix() const604 const SkMatrix& viewMatrix() const { return fViewMatrix; }
miterStroke() const605 bool miterStroke() const { return fMiterStroke; }
606
607 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps&) override;
608
609 void generateAAStrokeRectGeometry(VertexWriter& vertices,
610 const SkPMColor4f& color,
611 bool wideColor,
612 const SkRect& devOutside,
613 const SkRect& devOutsideAssist,
614 const SkRect& devInside,
615 bool miterStroke,
616 bool degenerate,
617 const SkVector& devHalfStrokeSize,
618 bool usesMSAASurface) const;
619
620 Helper fHelper;
621 STArray<1, RectInfo, true> fRects;
622 SkMatrix fViewMatrix;
623 GrSimpleMesh* fMesh = nullptr;
624 GrProgramInfo* fProgramInfo = nullptr;
625 bool fMiterStroke;
626 bool fWideColor;
627
628 using INHERITED = GrMeshDrawOp;
629 };
630
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && appliedClip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)631 void AAStrokeRectOp::onCreateProgramInfo(const GrCaps* caps,
632 SkArenaAlloc* arena,
633 const GrSurfaceProxyView& writeView,
634 bool usesMSAASurface,
635 GrAppliedClip&& appliedClip,
636 const GrDstProxyView& dstProxyView,
637 GrXferBarrierFlags renderPassXferBarriers,
638 GrLoadOp colorLoadOp) {
639
640 GrGeometryProcessor* gp = create_aa_stroke_rect_gp(
641 arena,
642 usesMSAASurface,
643 this->compatibleWithCoverageAsAlpha(usesMSAASurface),
644 this->viewMatrix(),
645 fHelper.usesLocalCoords(),
646 fWideColor);
647 if (!gp) {
648 SkDebugf("Couldn't create GrGeometryProcessor\n");
649 return;
650 }
651
652 fProgramInfo = fHelper.createProgramInfo(caps,
653 arena,
654 writeView,
655 usesMSAASurface,
656 std::move(appliedClip),
657 dstProxyView,
658 gp,
659 GrPrimitiveType::kTriangles,
660 renderPassXferBarriers,
661 colorLoadOp);
662 }
663
onPrepareDraws(GrMeshDrawTarget * target)664 void AAStrokeRectOp::onPrepareDraws(GrMeshDrawTarget* target) {
665
666 if (!fProgramInfo) {
667 this->createProgramInfo(target);
668 if (!fProgramInfo) {
669 return;
670 }
671 }
672
673 int innerVertexNum = 4;
674 int outerVertexNum = this->miterStroke() ? 4 : 8;
675 int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2;
676 int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt;
677 int instanceCount = fRects.size();
678 int maxQuads = this->miterStroke() ? kNumMiterRectsInIndexBuffer : kNumBevelRectsInIndexBuffer;
679
680 sk_sp<const GrGpuBuffer> indexBuffer =
681 GetIndexBuffer(target->resourceProvider(), this->miterStroke());
682 if (!indexBuffer) {
683 SkDebugf("Could not allocate indices\n");
684 return;
685 }
686 PatternHelper helper(target, GrPrimitiveType::kTriangles,
687 fProgramInfo->geomProc().vertexStride(), std::move(indexBuffer),
688 verticesPerInstance, indicesPerInstance, instanceCount, maxQuads);
689 VertexWriter vertices{ helper.vertices() };
690 if (!vertices) {
691 SkDebugf("Could not allocate vertices\n");
692 return;
693 }
694
695 for (int i = 0; i < instanceCount; i++) {
696 const RectInfo& info = fRects[i];
697 this->generateAAStrokeRectGeometry(vertices,
698 info.fColor,
699 fWideColor,
700 info.fDevOutside,
701 info.fDevOutsideAssist,
702 info.fDevInside,
703 fMiterStroke,
704 info.fDegenerate,
705 info.fDevHalfStrokeSize,
706 target->usesMSAASurface());
707 }
708 fMesh = helper.mesh();
709 }
710
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)711 void AAStrokeRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
712 if (!fProgramInfo || !fMesh) {
713 return;
714 }
715
716 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
717 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
718 flushState->drawMesh(*fMesh);
719 }
720
GetIndexBuffer(GrResourceProvider * resourceProvider,bool miterStroke)721 sk_sp<const GrGpuBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider,
722 bool miterStroke) {
723 if (miterStroke) {
724 // clang-format off
725 static const uint16_t gMiterIndices[] = {
726 0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0,
727 1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0,
728 2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0,
729 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0,
730
731 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4,
732 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4,
733 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4,
734 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4,
735
736 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8,
737 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8,
738 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8,
739 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8,
740 };
741 // clang-format on
742 static_assert(std::size(gMiterIndices) == kMiterIndexCnt);
743 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
744 return resourceProvider->findOrCreatePatternedIndexBuffer(
745 gMiterIndices, kMiterIndexCnt, kNumMiterRectsInIndexBuffer, kMiterVertexCnt,
746 gMiterIndexBufferKey);
747 } else {
748 /**
749 * As in miter-stroke, index = a + b, and a is the current index, b is the shift
750 * from the first index. The index layout:
751 * outer AA line: 0~3, 4~7
752 * outer edge: 8~11, 12~15
753 * inner edge: 16~19
754 * inner AA line: 20~23
755 * Following comes a bevel-stroke rect and its indices:
756 *
757 * 4 7
758 * *********************************
759 * * ______________________________ *
760 * * / 12 15 \ *
761 * * / \ *
762 * 0 * |8 16_____________________19 11 | * 3
763 * * | | | | *
764 * * | | **************** | | *
765 * * | | * 20 23 * | | *
766 * * | | * * | | *
767 * * | | * 21 22 * | | *
768 * * | | **************** | | *
769 * * | |____________________| | *
770 * 1 * |9 17 18 10| * 2
771 * * \ / *
772 * * \13 __________________________14/ *
773 * * *
774 * **********************************
775 * 5 6
776 */
777 // clang-format off
778 static const uint16_t gBevelIndices[] = {
779 // Draw outer AA, from outer AA line to outer edge, shift is 0.
780 0 + 0, 1 + 0, 9 + 0, 9 + 0, 8 + 0, 0 + 0,
781 1 + 0, 5 + 0, 13 + 0, 13 + 0, 9 + 0, 1 + 0,
782 5 + 0, 6 + 0, 14 + 0, 14 + 0, 13 + 0, 5 + 0,
783 6 + 0, 2 + 0, 10 + 0, 10 + 0, 14 + 0, 6 + 0,
784 2 + 0, 3 + 0, 11 + 0, 11 + 0, 10 + 0, 2 + 0,
785 3 + 0, 7 + 0, 15 + 0, 15 + 0, 11 + 0, 3 + 0,
786 7 + 0, 4 + 0, 12 + 0, 12 + 0, 15 + 0, 7 + 0,
787 4 + 0, 0 + 0, 8 + 0, 8 + 0, 12 + 0, 4 + 0,
788
789 // Draw the stroke, from outer edge to inner edge, shift is 8.
790 0 + 8, 1 + 8, 9 + 8, 9 + 8, 8 + 8, 0 + 8,
791 1 + 8, 5 + 8, 9 + 8,
792 5 + 8, 6 + 8, 10 + 8, 10 + 8, 9 + 8, 5 + 8,
793 6 + 8, 2 + 8, 10 + 8,
794 2 + 8, 3 + 8, 11 + 8, 11 + 8, 10 + 8, 2 + 8,
795 3 + 8, 7 + 8, 11 + 8,
796 7 + 8, 4 + 8, 8 + 8, 8 + 8, 11 + 8, 7 + 8,
797 4 + 8, 0 + 8, 8 + 8,
798
799 // Draw the inner AA, from inner edge to inner AA line, shift is 16.
800 0 + 16, 1 + 16, 5 + 16, 5 + 16, 4 + 16, 0 + 16,
801 1 + 16, 2 + 16, 6 + 16, 6 + 16, 5 + 16, 1 + 16,
802 2 + 16, 3 + 16, 7 + 16, 7 + 16, 6 + 16, 2 + 16,
803 3 + 16, 0 + 16, 4 + 16, 4 + 16, 7 + 16, 3 + 16,
804 };
805 // clang-format on
806 static_assert(std::size(gBevelIndices) == kBevelIndexCnt);
807
808 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
809 return resourceProvider->findOrCreatePatternedIndexBuffer(
810 gBevelIndices, kBevelIndexCnt, kNumBevelRectsInIndexBuffer, kBevelVertexCnt,
811 gBevelIndexBufferKey);
812 }
813 }
814
onCombineIfPossible(GrOp * t,SkArenaAlloc *,const GrCaps & caps)815 GrOp::CombineResult AAStrokeRectOp::onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps)
816 {
817 AAStrokeRectOp* that = t->cast<AAStrokeRectOp>();
818
819 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
820 return CombineResult::kCannotCombine;
821 }
822
823 // TODO combine across miterstroke changes
824 if (this->miterStroke() != that->miterStroke()) {
825 return CombineResult::kCannotCombine;
826 }
827
828 // We apply the viewmatrix to the rect points on the cpu. However, if the pipeline uses
829 // local coords then we won't be able to combine. TODO: Upload local coords as an attribute.
830 if (fHelper.usesLocalCoords() &&
831 !SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix()))
832 {
833 return CombineResult::kCannotCombine;
834 }
835
836 fRects.push_back_n(that->fRects.size(), that->fRects.begin());
837 fWideColor |= that->fWideColor;
838 return CombineResult::kMerged;
839 }
840
generateAAStrokeRectGeometry(VertexWriter & vertices,const SkPMColor4f & color,bool wideColor,const SkRect & devOutside,const SkRect & devOutsideAssist,const SkRect & devInside,bool miterStroke,bool degenerate,const SkVector & devHalfStrokeSize,bool usesMSAASurface) const841 void AAStrokeRectOp::generateAAStrokeRectGeometry(VertexWriter& vertices,
842 const SkPMColor4f& color,
843 bool wideColor,
844 const SkRect& devOutside,
845 const SkRect& devOutsideAssist,
846 const SkRect& devInside,
847 bool miterStroke,
848 bool degenerate,
849 const SkVector& devHalfStrokeSize,
850 bool usesMSAASurface) const {
851 // We create vertices for four nested rectangles. There are two ramps from 0 to full
852 // coverage, one on the exterior of the stroke and the other on the interior.
853
854 // The following code only works if either devStrokeSize's fX and fY are
855 // equal (in which case innerCoverage is same for all sides of the rects) or
856 // if devStrokeSize's fX and fY are both greater than 1.0 (in which case
857 // innerCoverage will always be 1).
858 SkASSERT(stroke_dev_half_size_supported(devHalfStrokeSize));
859
860 auto inset_fan = [](const SkRect& r, SkScalar dx, SkScalar dy) {
861 return VertexWriter::TriFanFromRect(r.makeInset(dx, dy));
862 };
863
864 bool tweakAlphaForCoverage = this->compatibleWithCoverageAsAlpha(usesMSAASurface);
865
866 auto maybe_coverage = [tweakAlphaForCoverage](float coverage) {
867 return VertexWriter::If(!tweakAlphaForCoverage, coverage);
868 };
869
870 // How much do we inset toward the inside of the strokes?
871 float inset = std::min(0.5f, std::min(devHalfStrokeSize.fX, devHalfStrokeSize.fY));
872 float innerCoverage = 1;
873 if (inset < 0.5f) {
874 // Stroke is subpixel, so reduce the coverage to simulate the narrower strokes.
875 innerCoverage = 2 * inset / (inset + .5f);
876 }
877
878 // How much do we outset away from the outside of the strokes?
879 // We always want to keep the AA picture frame one pixel wide.
880 float outset = 1 - inset;
881 float outerCoverage = 0;
882
883 // How much do we outset away from the interior side of the stroke (toward the center)?
884 float interiorOutset = outset;
885 float interiorCoverage = outerCoverage;
886
887 if (usesMSAASurface) {
888 // Since we're using MSAA, extend our outsets to ensure any pixel with partial coverage has
889 // a full sample mask.
890 constexpr float msaaExtraBloat = SK_ScalarSqrt2 - .5f;
891 outset += msaaExtraBloat;
892 outerCoverage -= msaaExtraBloat;
893
894 float insetExtraBloat =
895 std::min(inset + msaaExtraBloat,
896 std::min(devHalfStrokeSize.fX, devHalfStrokeSize.fY)) - inset;
897 inset += insetExtraBloat;
898 innerCoverage += insetExtraBloat;
899
900 float interiorExtraBloat =
901 std::min(interiorOutset + msaaExtraBloat,
902 std::min(devInside.width(), devInside.height()) / 2) - interiorOutset;
903 interiorOutset += interiorExtraBloat;
904 interiorCoverage -= interiorExtraBloat;
905 }
906
907 VertexColor innerColor(tweakAlphaForCoverage ? color * innerCoverage : color, wideColor);
908 VertexColor outerColor(tweakAlphaForCoverage ? SK_PMColor4fTRANSPARENT : color, wideColor);
909
910 // Exterior outset rect (away from stroke).
911 vertices.writeQuad(inset_fan(devOutside, -outset, -outset),
912 outerColor,
913 maybe_coverage(outerCoverage));
914
915 if (!miterStroke) {
916 // Second exterior outset.
917 vertices.writeQuad(inset_fan(devOutsideAssist, -outset, -outset),
918 outerColor,
919 maybe_coverage(outerCoverage));
920 }
921
922 // Exterior inset rect (toward stroke).
923 vertices.writeQuad(inset_fan(devOutside, inset, inset),
924 innerColor,
925 maybe_coverage(innerCoverage));
926
927 if (!miterStroke) {
928 // Second exterior inset.
929 vertices.writeQuad(inset_fan(devOutsideAssist, inset, inset),
930 innerColor,
931 maybe_coverage(innerCoverage));
932 }
933
934 if (!degenerate) {
935 // Interior inset rect (toward stroke).
936 vertices.writeQuad(inset_fan(devInside, -inset, -inset),
937 innerColor,
938 maybe_coverage(innerCoverage));
939
940 // Interior outset rect (away from stroke, toward center of rect).
941 SkRect interiorAABoundary = devInside.makeInset(interiorOutset, interiorOutset);
942 float coverageBackset = 0; // Adds back coverage when the interior AA edges cross.
943 if (interiorAABoundary.fLeft > interiorAABoundary.fRight) {
944 coverageBackset =
945 (interiorAABoundary.fLeft - interiorAABoundary.fRight) / (interiorOutset * 2);
946 interiorAABoundary.fLeft = interiorAABoundary.fRight = interiorAABoundary.centerX();
947 }
948 if (interiorAABoundary.fTop > interiorAABoundary.fBottom) {
949 coverageBackset = std::max(
950 (interiorAABoundary.fTop - interiorAABoundary.fBottom) / (interiorOutset * 2),
951 coverageBackset);
952 interiorAABoundary.fTop = interiorAABoundary.fBottom = interiorAABoundary.centerY();
953 }
954 if (coverageBackset > 0) {
955 // The interior edges crossed. Lerp back toward innerCoverage, which is what this op
956 // will draw in the degenerate case. This gives a smooth transition into the degenerate
957 // case.
958 interiorCoverage += interiorCoverage * (1 - coverageBackset) +
959 innerCoverage * coverageBackset;
960 }
961 VertexColor interiorColor(tweakAlphaForCoverage ? color * interiorCoverage : color,
962 wideColor);
963 vertices.writeQuad(VertexWriter::TriFanFromRect(interiorAABoundary),
964 interiorColor,
965 maybe_coverage(interiorCoverage));
966 } else {
967 // When the interior rect has become degenerate we smoosh to a single point
968 SkASSERT(devInside.fLeft == devInside.fRight && devInside.fTop == devInside.fBottom);
969
970 vertices.writeQuad(VertexWriter::TriFanFromRect(devInside),
971 innerColor,
972 maybe_coverage(innerCoverage));
973
974 // ... unless we are degenerate, in which case we must apply the scaled coverage
975 vertices.writeQuad(VertexWriter::TriFanFromRect(devInside),
976 innerColor,
977 maybe_coverage(innerCoverage));
978 }
979 }
980
981 } // anonymous namespace
982
Make(GrRecordingContext * context,GrPaint && paint,GrAAType aaType,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke)983 GrOp::Owner Make(GrRecordingContext* context,
984 GrPaint&& paint,
985 GrAAType aaType,
986 const SkMatrix& viewMatrix,
987 const SkRect& rect,
988 const SkStrokeRec& stroke) {
989 SkASSERT(!context->priv().caps()->reducedShaderMode());
990 if (aaType == GrAAType::kCoverage) {
991 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
992 } else {
993 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
994 }
995 }
996
MakeNested(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkRect rects[2])997 GrOp::Owner MakeNested(GrRecordingContext* context,
998 GrPaint&& paint,
999 const SkMatrix& viewMatrix,
1000 const SkRect rects[2]) {
1001 SkASSERT(viewMatrix.rectStaysRect());
1002 SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty());
1003
1004 SkRect devOutside = viewMatrix.mapRect(rects[0]);
1005 SkRect devInside = viewMatrix.mapRect(rects[1]);
1006 float dx = devOutside.fRight - devInside.fRight;
1007 float dy = devOutside.fBottom - devInside.fBottom;
1008
1009 // Clips our draw rects 1 full pixel outside the viewport. This avoids interpolation precision
1010 // issues with very large coordinates.
1011 const float m = context->priv().caps()->maxRenderTargetSize();
1012 const SkRect visibilityBounds = SkRect::MakeWH(m, m).makeOutset(1, 1);
1013
1014 if (!devOutside.intersect(visibilityBounds.makeOutset(dx, dy))) {
1015 return nullptr;
1016 }
1017
1018 if (devInside.isEmpty() || !devInside.intersect(visibilityBounds)) {
1019 if (devOutside.isEmpty()) {
1020 return nullptr;
1021 }
1022 DrawQuad quad{GrQuad::MakeFromRect(rects[0], viewMatrix), GrQuad(rects[0]),
1023 GrQuadAAFlags::kAll};
1024 return ganesh::FillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, &quad);
1025 }
1026
1027 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside,
1028 devInside, SkVector{dx, dy} * .5f);
1029 }
1030
1031 } // namespace skgpu::ganesh::StrokeRectOp
1032
1033 #if defined(GPU_TEST_UTILS)
1034
GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp)1035 GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
1036 SkMatrix viewMatrix = GrTest::TestMatrix(random);
1037 SkRect rect = GrTest::TestRect(random);
1038 SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
1039 SkPaint strokePaint;
1040 strokePaint.setStrokeWidth(strokeWidth);
1041 strokePaint.setStyle(SkPaint::kStroke_Style);
1042 strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
1043 SkStrokeRec strokeRec(strokePaint);
1044 GrAAType aaType = GrAAType::kNone;
1045 if (numSamples > 1) {
1046 aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
1047 }
1048 return skgpu::ganesh::StrokeRectOp::NonAAStrokeRectOp::Make(context, std::move(paint),
1049 viewMatrix, rect, strokeRec,
1050 aaType);
1051 }
1052
GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp)1053 GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) {
1054 bool miterStroke = random->nextBool();
1055
1056 // Create either a empty rect or a non-empty rect.
1057 SkRect rect =
1058 random->nextBool() ? SkRect::MakeXYWH(10, 10, 50, 40) : SkRect::MakeXYWH(6, 7, 0, 0);
1059 SkScalar minDim = std::min(rect.width(), rect.height());
1060 SkScalar strokeWidth = random->nextUScalar1() * minDim;
1061
1062 SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
1063 rec.setStrokeStyle(strokeWidth);
1064 rec.setStrokeParams(SkPaint::kButt_Cap,
1065 miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f);
1066 SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random);
1067 return skgpu::ganesh::StrokeRectOp::AAStrokeRectOp::Make(context, std::move(paint), matrix,
1068 rect, rec);
1069 }
1070
1071 #endif
1072