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/ganesh/ops/FillRRectOp.h"
9
10 #include "include/gpu/GrRecordingContext.h"
11 #include "src/base/SkVx.h"
12 #include "src/core/SkRRectPriv.h"
13 #include "src/gpu/BufferWriter.h"
14 #include "src/gpu/KeyBuilder.h"
15 #include "src/gpu/ganesh/GrCaps.h"
16 #include "src/gpu/ganesh/GrGeometryProcessor.h"
17 #include "src/gpu/ganesh/GrMemoryPool.h"
18 #include "src/gpu/ganesh/GrOpFlushState.h"
19 #include "src/gpu/ganesh/GrOpsRenderPass.h"
20 #include "src/gpu/ganesh/GrProgramInfo.h"
21 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
22 #include "src/gpu/ganesh/GrResourceProvider.h"
23 #include "src/gpu/ganesh/geometry/GrShape.h"
24 #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
25 #include "src/gpu/ganesh/glsl/GrGLSLVarying.h"
26 #include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h"
27 #include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
28 #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h"
29
30 namespace skgpu::v1::FillRRectOp {
31
32 namespace {
33
34 class FillRRectOpImpl final : public GrMeshDrawOp {
35 private:
36 using Helper = GrSimpleMeshDrawOpHelper;
37
38 public:
39 DEFINE_OP_CLASS_ID
40
41 struct LocalCoords {
42 enum class Type : bool { kRect, kMatrix };
LocalCoordsskgpu::v1::FillRRectOp::__anon0260c85c0111::FillRRectOpImpl::LocalCoords43 LocalCoords(const SkRect& localRect)
44 : fType(Type::kRect)
45 , fRect(localRect) {}
LocalCoordsskgpu::v1::FillRRectOp::__anon0260c85c0111::FillRRectOpImpl::LocalCoords46 LocalCoords(const SkMatrix& localMatrix)
47 : fType(Type::kMatrix)
48 , fMatrix(localMatrix) {}
49 Type fType;
50 union {
51 SkRect fRect;
52 SkMatrix fMatrix;
53 };
54 };
55
56 static GrOp::Owner Make(GrRecordingContext*,
57 SkArenaAlloc*,
58 GrPaint&&,
59 const SkMatrix& viewMatrix,
60 const SkRRect&,
61 const LocalCoords&,
62 GrAA);
63
name() const64 const char* name() const override { return "FillRRectOp"; }
65
fixedFunctionFlags() const66 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
67
68 ClipResult clipToShape(skgpu::v1::SurfaceDrawContext*,
69 SkClipOp,
70 const SkMatrix& clipMatrix,
71 const GrShape&,
72 GrAA) override;
73
74 GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override;
75 CombineResult onCombineIfPossible(GrOp*, SkArenaAlloc*, const GrCaps&) override;
76
visitProxies(const GrVisitProxyFunc & func) const77 void visitProxies(const GrVisitProxyFunc& func) const override {
78 if (fProgramInfo) {
79 fProgramInfo->visitFPProxies(func);
80 } else {
81 fHelper.visitProxies(func);
82 }
83 }
84
85 void onPrepareDraws(GrMeshDrawTarget*) override;
86
87 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
88
89 private:
90 friend class ::GrSimpleMeshDrawOpHelper; // for access to ctor
91 friend class ::GrOp; // for access to ctor
92
93 enum class ProcessorFlags {
94 kNone = 0,
95 kUseHWDerivatives = 1 << 0,
96 kHasLocalCoords = 1 << 1,
97 kWideColor = 1 << 2,
98 kMSAAEnabled = 1 << 3,
99 kFakeNonAA = 1 << 4,
100 };
101 constexpr static int kNumProcessorFlags = 5;
102
103 GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(ProcessorFlags);
104
105 class Processor;
106
107 FillRRectOpImpl(GrProcessorSet*,
108 const SkPMColor4f& paintColor,
109 SkArenaAlloc*,
110 const SkMatrix& viewMatrix,
111 const SkRRect&,
112 const LocalCoords&,
113 ProcessorFlags);
114
programInfo()115 GrProgramInfo* programInfo() override { return fProgramInfo; }
116
117 // Create a GrProgramInfo object in the provided arena
118 void onCreateProgramInfo(const GrCaps*,
119 SkArenaAlloc*,
120 const GrSurfaceProxyView& writeView,
121 bool usesMSAASurface,
122 GrAppliedClip&&,
123 const GrDstProxyView&,
124 GrXferBarrierFlags renderPassXferBarriers,
125 GrLoadOp colorLoadOp) override;
126
127 Helper fHelper;
128 ProcessorFlags fProcessorFlags;
129
130 struct Instance {
Instanceskgpu::v1::FillRRectOp::__anon0260c85c0111::FillRRectOpImpl::Instance131 Instance(const SkMatrix& viewMatrix,
132 const SkRRect& rrect,
133 const LocalCoords& localCoords,
134 const SkPMColor4f& color)
135 : fViewMatrix(viewMatrix), fRRect(rrect), fLocalCoords(localCoords), fColor(color) {
136 }
137 SkMatrix fViewMatrix;
138 SkRRect fRRect;
139 LocalCoords fLocalCoords;
140 SkPMColor4f fColor;
141 Instance* fNext = nullptr;
142 };
143
144 Instance* fHeadInstance;
145 Instance** fTailInstance;
146 int fInstanceCount = 1;
147
148 sk_sp<const GrBuffer> fInstanceBuffer;
149 sk_sp<const GrBuffer> fVertexBuffer;
150 sk_sp<const GrBuffer> fIndexBuffer;
151 int fBaseInstance = 0;
152
153 // If this op is prePrepared the created programInfo will be stored here for use in
154 // onExecute. In the prePrepared case it will have been stored in the record-time arena.
155 GrProgramInfo* fProgramInfo = nullptr;
156 };
157
158 GR_MAKE_BITFIELD_CLASS_OPS(FillRRectOpImpl::ProcessorFlags)
159
160 // Hardware derivatives are not always accurate enough for highly elliptical corners. This method
161 // checks to make sure the corners will still all look good if we use HW derivatives.
162 bool can_use_hw_derivatives_with_coverage(const GrShaderCaps&,
163 const SkMatrix&,
164 const SkRRect&);
165
Make(GrRecordingContext * ctx,SkArenaAlloc * arena,GrPaint && paint,const SkMatrix & viewMatrix,const SkRRect & rrect,const LocalCoords & localCoords,GrAA aa)166 GrOp::Owner FillRRectOpImpl::Make(GrRecordingContext* ctx,
167 SkArenaAlloc* arena,
168 GrPaint&& paint,
169 const SkMatrix& viewMatrix,
170 const SkRRect& rrect,
171 const LocalCoords& localCoords,
172 GrAA aa) {
173 const GrCaps* caps = ctx->priv().caps();
174
175 if (!caps->drawInstancedSupport()) {
176 return nullptr;
177 }
178
179 // We transform into a normalized -1..+1 space to draw the round rect. If the boundaries are too
180 // large, the math can overflow. The caller can fall back on path rendering if this is the case.
181 if (std::max(rrect.height(), rrect.width()) >= 1e6f) {
182 return nullptr;
183 }
184
185 ProcessorFlags flags = ProcessorFlags::kNone;
186 // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we already
187 // use HW derivatives. The only trick will be adjusting the AA outset to account for
188 // perspective. (i.e., outset = 0.5 * z.)
189 if (viewMatrix.hasPerspective()) {
190 return nullptr;
191 }
192 if (can_use_hw_derivatives_with_coverage(*caps->shaderCaps(), viewMatrix, rrect)) {
193 // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms in
194 // coverage mode. We use them as long as the approximation will be accurate enough.
195 flags |= ProcessorFlags::kUseHWDerivatives;
196 }
197 if (aa == GrAA::kNo) {
198 flags |= ProcessorFlags::kFakeNonAA;
199 }
200
201 return Helper::FactoryHelper<FillRRectOpImpl>(ctx, std::move(paint), arena, viewMatrix, rrect,
202 localCoords, flags);
203 }
204
FillRRectOpImpl(GrProcessorSet * processorSet,const SkPMColor4f & paintColor,SkArenaAlloc * arena,const SkMatrix & viewMatrix,const SkRRect & rrect,const LocalCoords & localCoords,ProcessorFlags processorFlags)205 FillRRectOpImpl::FillRRectOpImpl(GrProcessorSet* processorSet,
206 const SkPMColor4f& paintColor,
207 SkArenaAlloc* arena,
208 const SkMatrix& viewMatrix,
209 const SkRRect& rrect,
210 const LocalCoords& localCoords,
211 ProcessorFlags processorFlags)
212 : GrMeshDrawOp(ClassID())
213 , fHelper(processorSet,
214 (processorFlags & ProcessorFlags::kFakeNonAA)
215 ? GrAAType::kNone
216 : GrAAType::kCoverage) // Use analytic AA even if the RT is MSAA.
217 , fProcessorFlags(processorFlags & ~(ProcessorFlags::kHasLocalCoords |
218 ProcessorFlags::kWideColor |
219 ProcessorFlags::kMSAAEnabled))
220 , fHeadInstance(arena->make<Instance>(viewMatrix, rrect, localCoords, paintColor))
221 , fTailInstance(&fHeadInstance->fNext) {
222 // FillRRectOp::Make fails if there is perspective.
223 SkASSERT(!viewMatrix.hasPerspective());
224 this->setBounds(viewMatrix.mapRect(rrect.getBounds()),
225 GrOp::HasAABloat(!(processorFlags & ProcessorFlags::kFakeNonAA)),
226 GrOp::IsHairline::kNo);
227 }
228
clipToShape(skgpu::v1::SurfaceDrawContext * sdc,SkClipOp clipOp,const SkMatrix & clipMatrix,const GrShape & shape,GrAA aa)229 GrDrawOp::ClipResult FillRRectOpImpl::clipToShape(skgpu::v1::SurfaceDrawContext* sdc,
230 SkClipOp clipOp,
231 const SkMatrix& clipMatrix,
232 const GrShape& shape,
233 GrAA aa) {
234 SkASSERT(fInstanceCount == 1); // This needs to be called before combining.
235 SkASSERT(fHeadInstance->fNext == nullptr);
236
237 if ((shape.isRect() || shape.isRRect()) &&
238 clipOp == SkClipOp::kIntersect &&
239 (aa == GrAA::kNo) == (fProcessorFlags & ProcessorFlags::kFakeNonAA)) {
240 // The clip shape is a round rect. Attempt to map it to a round rect in "viewMatrix" space.
241 SkRRect clipRRect;
242 if (clipMatrix == fHeadInstance->fViewMatrix) {
243 if (shape.isRect()) {
244 clipRRect.setRect(shape.rect());
245 } else {
246 clipRRect = shape.rrect();
247 }
248 } else {
249 // Find a matrix that maps from "clipMatrix" space to "viewMatrix" space.
250 SkASSERT(!fHeadInstance->fViewMatrix.hasPerspective());
251 if (clipMatrix.hasPerspective()) {
252 return ClipResult::kFail;
253 }
254 SkMatrix clipToView;
255 if (!fHeadInstance->fViewMatrix.invert(&clipToView)) {
256 return ClipResult::kClippedOut;
257 }
258 clipToView.preConcat(clipMatrix);
259 SkASSERT(!clipToView.hasPerspective());
260 if (!SkScalarNearlyZero(clipToView.getSkewX()) ||
261 !SkScalarNearlyZero(clipToView.getSkewY())) {
262 // A rect in "clipMatrix" space is not a rect in "viewMatrix" space.
263 return ClipResult::kFail;
264 }
265 clipToView.setSkewX(0);
266 clipToView.setSkewY(0);
267 SkASSERT(clipToView.rectStaysRect());
268
269 if (shape.isRect()) {
270 clipRRect.setRect(clipToView.mapRect(shape.rect()));
271 } else {
272 if (!shape.rrect().transform(clipToView, &clipRRect)) {
273 // Transforming the rrect failed. This shouldn't generally happen except in
274 // cases of fp32 overflow.
275 return ClipResult::kFail;
276 }
277 }
278 }
279
280 // Intersect our round rect with the clip shape.
281 SkRRect isectRRect;
282 if (fHeadInstance->fRRect.isRect() && clipRRect.isRect()) {
283 SkRect isectRect;
284 if (!isectRect.intersect(fHeadInstance->fRRect.rect(), clipRRect.rect())) {
285 return ClipResult::kClippedOut;
286 }
287 isectRRect.setRect(isectRect);
288 } else {
289 isectRRect = SkRRectPriv::ConservativeIntersect(fHeadInstance->fRRect, clipRRect);
290 if (isectRRect.isEmpty()) {
291 // The round rects did not intersect at all or the intersection was too complicated
292 // to compute quickly.
293 return ClipResult::kFail;
294 }
295 }
296
297 // Don't apply the clip geometrically if it becomes subpixel, since then the hairline
298 // rendering may outset beyond the original clip.
299 SkRect devISectBounds = fHeadInstance->fViewMatrix.mapRect(isectRRect.rect());
300 if (devISectBounds.width() < 1.f || devISectBounds.height() < 1.f) {
301 return ClipResult::kFail;
302 }
303
304 if (fHeadInstance->fLocalCoords.fType == LocalCoords::Type::kRect) {
305 // Update the local rect.
306 auto rect = skvx::bit_pun<skvx::float4>(fHeadInstance->fRRect.rect());
307 auto local = skvx::bit_pun<skvx::float4>(fHeadInstance->fLocalCoords.fRect);
308 auto isect = skvx::bit_pun<skvx::float4>(isectRRect.rect());
309 auto rectToLocalSize = (local - skvx::shuffle<2,3,0,1>(local)) /
310 (rect - skvx::shuffle<2,3,0,1>(rect));
311 fHeadInstance->fLocalCoords.fRect =
312 skvx::bit_pun<SkRect>((isect - rect) * rectToLocalSize + local);
313 }
314
315 // Update the round rect.
316 fHeadInstance->fRRect = isectRRect;
317 return ClipResult::kClippedGeometrically;
318 }
319
320 return ClipResult::kFail;
321 }
322
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)323 GrProcessorSet::Analysis FillRRectOpImpl::finalize(const GrCaps& caps, const GrAppliedClip* clip,
324 GrClampType clampType) {
325 SkASSERT(fInstanceCount == 1);
326 SkASSERT(fHeadInstance->fNext == nullptr);
327
328 bool isWideColor;
329 auto analysis = fHelper.finalizeProcessors(caps, clip, clampType,
330 GrProcessorAnalysisCoverage::kSingleChannel,
331 &fHeadInstance->fColor, &isWideColor);
332 if (isWideColor) {
333 fProcessorFlags |= ProcessorFlags::kWideColor;
334 }
335 if (analysis.usesLocalCoords()) {
336 fProcessorFlags |= ProcessorFlags::kHasLocalCoords;
337 }
338 return analysis;
339 }
340
onCombineIfPossible(GrOp * op,SkArenaAlloc *,const GrCaps & caps)341 GrOp::CombineResult FillRRectOpImpl::onCombineIfPossible(GrOp* op,
342 SkArenaAlloc*,
343 const GrCaps& caps) {
344 auto that = op->cast<FillRRectOpImpl>();
345 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds()) ||
346 fProcessorFlags != that->fProcessorFlags) {
347 return CombineResult::kCannotCombine;
348 }
349
350 *fTailInstance = that->fHeadInstance;
351 fTailInstance = that->fTailInstance;
352 fInstanceCount += that->fInstanceCount;
353 return CombineResult::kMerged;
354 }
355
356 class FillRRectOpImpl::Processor final : public GrGeometryProcessor {
357 public:
Make(SkArenaAlloc * arena,GrAAType aaType,ProcessorFlags flags)358 static GrGeometryProcessor* Make(SkArenaAlloc* arena, GrAAType aaType, ProcessorFlags flags) {
359 return arena->make([&](void* ptr) {
360 return new (ptr) Processor(aaType, flags);
361 });
362 }
363
name() const364 const char* name() const override { return "FillRRectOp::Processor"; }
365
addToKey(const GrShaderCaps & caps,KeyBuilder * b) const366 void addToKey(const GrShaderCaps& caps, KeyBuilder* b) const override {
367 b->addBits(kNumProcessorFlags, (uint32_t)fFlags, "flags");
368 }
369
370 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override;
371
372 private:
373 class Impl;
374
Processor(GrAAType aaType,ProcessorFlags flags)375 Processor(GrAAType aaType, ProcessorFlags flags)
376 : GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID)
377 , fFlags(flags) {
378 this->setVertexAttributesWithImplicitOffsets(kVertexAttribs, std::size(kVertexAttribs));
379
380 fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
381 fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
382 fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
383 if (fFlags & ProcessorFlags::kHasLocalCoords) {
384 fInstanceAttribs.emplace_back("translate_and_localrotate",
385 kFloat4_GrVertexAttribType,
386 SkSLType::kFloat4);
387 fInstanceAttribs.emplace_back(
388 "localrect", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
389 } else {
390 fInstanceAttribs.emplace_back("translate_and_localrotate",
391 kFloat2_GrVertexAttribType,
392 SkSLType::kFloat2);
393 }
394 fColorAttrib = &fInstanceAttribs.push_back(
395 MakeColorAttribute("color", (fFlags & ProcessorFlags::kWideColor)));
396 SkASSERT(fInstanceAttribs.size() <= kMaxInstanceAttribs);
397 this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.begin(),
398 fInstanceAttribs.size());
399 }
400
401 inline static constexpr Attribute kVertexAttribs[] = {
402 {"radii_selector", kFloat4_GrVertexAttribType, SkSLType::kFloat4},
403 {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, SkSLType::kFloat4},
404 // Coverage only.
405 {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, SkSLType::kFloat4}};
406
407 const ProcessorFlags fFlags;
408
409 constexpr static int kMaxInstanceAttribs = 6;
410 SkSTArray<kMaxInstanceAttribs, Attribute> fInstanceAttribs;
411 const Attribute* fColorAttrib;
412 };
413
414 // Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear
415 // coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal
416 // edges. The Vertex struct tells the shader where to place its vertex within a normalized
417 // ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode.
418 struct CoverageVertex {
419 std::array<float, 4> fRadiiSelector;
420 std::array<float, 2> fCorner;
421 std::array<float, 2> fRadiusOutset;
422 std::array<float, 2> fAABloatDirection;
423 float fCoverage;
424 float fIsLinearCoverage;
425 };
426
427 // This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices
428 // of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than
429 // rectangles.
430 static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2);
431
432 static constexpr CoverageVertex kVertexData[] = {
433 // Left inset edge.
434 {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{+1,0}}, 1, 1},
435 {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{+1,0}}, 1, 1},
436
437 // Top inset edge.
438 {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,+1}}, 1, 1},
439 {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,+1}}, 1, 1},
440
441 // Right inset edge.
442 {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{-1,0}}, 1, 1},
443 {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{-1,0}}, 1, 1},
444
445 // Bottom inset edge.
446 {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,-1}}, 1, 1},
447 {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,-1}}, 1, 1},
448
449
450 // Left outset edge.
451 {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{-1,0}}, 0, 1},
452 {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{-1,0}}, 0, 1},
453
454 // Top outset edge.
455 {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,-1}}, 0, 1},
456 {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,-1}}, 0, 1},
457
458 // Right outset edge.
459 {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{+1,0}}, 0, 1},
460 {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{+1,0}}, 0, 1},
461
462 // Bottom outset edge.
463 {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,+1}}, 0, 1},
464 {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,+1}}, 0, 1},
465
466
467 // Top-left corner.
468 {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{-1, 0}}, 0, 0},
469 {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{+1, 0}}, 1, 0},
470 {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,+1}}, 1, 0},
471 {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,-1}}, 0, 0},
472 {{{1,0,0,0}}, {{-1,-1}}, {{+kOctoOffset,0}}, {{-1,-1}}, 0, 0},
473 {{{1,0,0,0}}, {{-1,-1}}, {{0,+kOctoOffset}}, {{-1,-1}}, 0, 0},
474
475 // Top-right corner.
476 {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,-1}}, 0, 0},
477 {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,+1}}, 1, 0},
478 {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{-1, 0}}, 1, 0},
479 {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{+1, 0}}, 0, 0},
480 {{{0,1,0,0}}, {{+1,-1}}, {{0,+kOctoOffset}}, {{+1,-1}}, 0, 0},
481 {{{0,1,0,0}}, {{+1,-1}}, {{-kOctoOffset,0}}, {{+1,-1}}, 0, 0},
482
483 // Bottom-right corner.
484 {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{+1, 0}}, 0, 0},
485 {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{-1, 0}}, 1, 0},
486 {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,-1}}, 1, 0},
487 {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,+1}}, 0, 0},
488 {{{0,0,1,0}}, {{+1,+1}}, {{-kOctoOffset,0}}, {{+1,+1}}, 0, 0},
489 {{{0,0,1,0}}, {{+1,+1}}, {{0,-kOctoOffset}}, {{+1,+1}}, 0, 0},
490
491 // Bottom-left corner.
492 {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,+1}}, 0, 0},
493 {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,-1}}, 1, 0},
494 {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{+1, 0}}, 1, 0},
495 {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{-1, 0}}, 0, 0},
496 {{{0,0,0,1}}, {{-1,+1}}, {{0,-kOctoOffset}}, {{-1,+1}}, 0, 0},
497 {{{0,0,0,1}}, {{-1,+1}}, {{+kOctoOffset,0}}, {{-1,+1}}, 0, 0}};
498
499 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey);
500
501 static constexpr uint16_t kIndexData[] = {
502 // Inset octagon (solid coverage).
503 0, 1, 7,
504 1, 2, 7,
505 7, 2, 6,
506 2, 3, 6,
507 6, 3, 5,
508 3, 4, 5,
509
510 // AA borders (linear coverage).
511 0, 1, 8, 1, 9, 8,
512 2, 3, 10, 3, 11, 10,
513 4, 5, 12, 5, 13, 12,
514 6, 7, 14, 7, 15, 14,
515
516 // Top-left arc.
517 16, 17, 21,
518 17, 21, 18,
519 21, 18, 20,
520 18, 20, 19,
521
522 // Top-right arc.
523 22, 23, 27,
524 23, 27, 24,
525 27, 24, 26,
526 24, 26, 25,
527
528 // Bottom-right arc.
529 28, 29, 33,
530 29, 33, 30,
531 33, 30, 32,
532 30, 32, 31,
533
534 // Bottom-left arc.
535 34, 35, 39,
536 35, 39, 36,
537 39, 36, 38,
538 36, 38, 37};
539
540 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
541
onPrepareDraws(GrMeshDrawTarget * target)542 void FillRRectOpImpl::onPrepareDraws(GrMeshDrawTarget* target) {
543 if (!fProgramInfo) {
544 this->createProgramInfo(target);
545 }
546
547 size_t instanceStride = fProgramInfo->geomProc().instanceStride();
548
549 if (VertexWriter instanceWriter = target->makeVertexWriter(instanceStride, fInstanceCount,
550 &fInstanceBuffer, &fBaseInstance)) {
551 SkDEBUGCODE(auto end = instanceWriter.mark(instanceStride * fInstanceCount));
552 for (Instance* i = fHeadInstance; i; i = i->fNext) {
553 auto [l, t, r, b] = i->fRRect.rect();
554
555 // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space.
556 SkMatrix m;
557 // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b].
558 m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2);
559 // Map to device space.
560 m.postConcat(i->fViewMatrix);
561
562 // Convert the radii to [-1, -1, +1, +1] space and write their attribs.
563 skvx::float4 radiiX, radiiY;
564 skvx::strided_load2(&SkRRectPriv::GetRadiiArray(i->fRRect)->fX, radiiX, radiiY);
565 radiiX *= 2 / (r - l);
566 radiiY *= 2 / (b - t);
567
568 instanceWriter << radiiX << radiiY
569 << m.getScaleX() << m.getSkewX() << m.getSkewY() << m.getScaleY()
570 << m.getTranslateX() << m.getTranslateY();
571
572 if (fProcessorFlags & ProcessorFlags::kHasLocalCoords) {
573 if (i->fLocalCoords.fType == LocalCoords::Type::kRect) {
574 instanceWriter << 0.f << 0.f // localrotate
575 << i->fLocalCoords.fRect; // localrect
576 } else {
577 SkASSERT(i->fLocalCoords.fType == LocalCoords::Type::kMatrix);
578 const SkRect& bounds = i->fRRect.rect();
579 const SkMatrix& localMatrix = i->fLocalCoords.fMatrix;
580 SkVector u = localMatrix.mapVector(bounds.right() - bounds.left(), 0);
581 SkVector v = localMatrix.mapVector(0, bounds.bottom() - bounds.top());
582 SkPoint l0 = localMatrix.mapPoint({bounds.left(), bounds.top()});
583 instanceWriter << v.x() << u.y() // localrotate
584 << l0 << (l0.x() + u.x()) << (l0.y() + v.y()); // localrect
585 }
586 }
587
588 instanceWriter << VertexColor(i->fColor, fProcessorFlags & ProcessorFlags::kWideColor);
589 }
590 SkASSERT(instanceWriter.mark() == end);
591 }
592
593 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
594
595 fIndexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
596 sizeof(kIndexData),
597 kIndexData, gIndexBufferKey);
598
599 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey);
600
601 fVertexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(GrGpuBufferType::kVertex,
602 sizeof(kVertexData),
603 kVertexData,
604 gVertexBufferKey);
605 }
606
607 class FillRRectOpImpl::Processor::Impl : public ProgramImpl {
608 public:
setData(const GrGLSLProgramDataManager &,const GrShaderCaps &,const GrGeometryProcessor &)609 void setData(const GrGLSLProgramDataManager&,
610 const GrShaderCaps&,
611 const GrGeometryProcessor&) override {}
612
613 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)614 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
615 GrGLSLVertexBuilder* v = args.fVertBuilder;
616 GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
617
618 const auto& proc = args.fGeomProc.cast<Processor>();
619 bool useHWDerivatives = (proc.fFlags & ProcessorFlags::kUseHWDerivatives);
620
621 SkASSERT(proc.vertexStride() == sizeof(CoverageVertex));
622
623 GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
624 varyings->emitAttributes(proc);
625 f->codeAppendf("half4 %s;", args.fOutputColor);
626 varyings->addPassThroughAttribute(proc.fColorAttrib->asShaderVar(),
627 args.fOutputColor,
628 GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
629
630 // Emit the vertex shader.
631 // When MSAA is enabled, we need to make sure every sample gets lit up on pixels that have
632 // fractional coverage. We do this by making the ramp wider.
633 v->codeAppendf("float aa_bloat_multiplier = %i;",
634 (proc.fFlags & ProcessorFlags::kMSAAEnabled)
635 ? 2 // Outset an entire pixel (2 radii).
636 : (!(proc.fFlags & ProcessorFlags::kFakeNonAA))
637 ? 1 // Outset one half pixel (1 radius).
638 : 0); // No AA bloat.
639
640 // Unpack vertex attribs.
641 v->codeAppend("float2 corner = corner_and_radius_outsets.xy;");
642 v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;");
643 v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;");
644 v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;");
645
646 // Find the amount to bloat each edge for AA (in source space).
647 v->codeAppend("float2 pixellength = inversesqrt("
648 "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));");
649 v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;");
650 v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + "
651 "abs(normalized_axis_dirs.zw));");
652 v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;");
653
654 // Identify our radii.
655 v->codeAppend("float4 radii_and_neighbors = radii_selector"
656 "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);");
657 v->codeAppend("float2 radii = radii_and_neighbors.xy;");
658 v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;");
659
660 v->codeAppend("float coverage_multiplier = 1;");
661 v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {");
662 // The rrect is more narrow than a half-pixel AA coverage ramp. We can't
663 // draw as-is or else opposite AA borders will overlap. Instead, fudge the
664 // size up to the width of a coverage ramp, and then reduce total coverage
665 // to make the rect appear more thin.
666 v->codeAppend( "corner = max(abs(corner), aa_bloatradius) * sign(corner);");
667 v->codeAppend( "coverage_multiplier = 1 / (max(aa_bloatradius.x, 1) * "
668 "max(aa_bloatradius.y, 1));");
669 // Set radii to zero to ensure we take the "linear coverage" codepath.
670 // (The "coverage" variable only has effect in the linear codepath.)
671 v->codeAppend( "radii = float2(0);");
672 v->codeAppend("}");
673
674 // Unpack coverage.
675 v->codeAppend("float coverage = aa_bloat_and_coverage.z;");
676 if (proc.fFlags & ProcessorFlags::kMSAAEnabled) {
677 // MSAA has a wider ramp that goes from -.5 to 1.5 instead of 0 to 1.
678 v->codeAppendf("coverage = (coverage - .5) * aa_bloat_multiplier + .5;");
679 }
680
681 v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.5))) {");
682 // The radii are very small. Demote this arc to a sharp 90 degree corner.
683 v->codeAppend( "radii = float2(0);");
684 // Convert to a standard picture frame for an AA rect instead of the round
685 // rect geometry.
686 v->codeAppend( "aa_bloat_direction = sign(corner);");
687 v->codeAppend( "if (coverage > .5) {"); // Are we an inset edge?
688 v->codeAppend( "aa_bloat_direction = -aa_bloat_direction;");
689 v->codeAppend( "}");
690 v->codeAppend( "is_linear_coverage = 1;");
691 v->codeAppend("} else {");
692 // Don't let radii get smaller than a coverage ramp plus an extra half
693 // pixel for MSAA. Always use the same amount so we don't pop when
694 // switching between MSAA and coverage.
695 v->codeAppend( "radii = clamp(radii, pixellength * 1.5, 2 - pixellength * 1.5);");
696 v->codeAppend( "neighbor_radii = clamp(neighbor_radii, pixellength * 1.5, "
697 "2 - pixellength * 1.5);");
698 // Don't let neighboring radii get closer together than 1/16 pixel.
699 v->codeAppend( "float2 spacing = 2 - radii - neighbor_radii;");
700 v->codeAppend( "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));");
701 v->codeAppend( "radii -= extra_pad * .5;");
702 v->codeAppend("}");
703
704 // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in
705 // normalized [-1,-1,+1,+1] space.
706 v->codeAppend("float2 aa_outset = "
707 "aa_bloat_direction * aa_bloatradius * aa_bloat_multiplier;");
708 v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;");
709
710 v->codeAppend("if (coverage > .5) {"); // Are we an inset edge?
711 // Don't allow the aa insets to overlap. i.e., Don't let them inset past
712 // the center (x=y=0). Since we don't allow the rect to become thinner
713 // than 1px, this should only happen when using MSAA, where we inset by an
714 // entire pixel instead of half.
715 v->codeAppend( "if (aa_bloat_direction.x != 0 && vertexpos.x * corner.x < 0) {");
716 v->codeAppend( "float backset = abs(vertexpos.x);");
717 v->codeAppend( "vertexpos.x = 0;");
718 v->codeAppend( "vertexpos.y += "
719 "backset * sign(corner.y) * pixellength.y/pixellength.x;");
720 v->codeAppend( "coverage = (coverage - .5) * abs(corner.x) / "
721 "(abs(corner.x) + backset) + .5;");
722 v->codeAppend( "}");
723 v->codeAppend( "if (aa_bloat_direction.y != 0 && vertexpos.y * corner.y < 0) {");
724 v->codeAppend( "float backset = abs(vertexpos.y);");
725 v->codeAppend( "vertexpos.y = 0;");
726 v->codeAppend( "vertexpos.x += "
727 "backset * sign(corner.x) * pixellength.x/pixellength.y;");
728 v->codeAppend( "coverage = (coverage - .5) * abs(corner.y) / "
729 "(abs(corner.y) + backset) + .5;");
730 v->codeAppend( "}");
731 v->codeAppend("}");
732
733 // Transform to device space.
734 v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
735 v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate_and_localrotate.xy;");
736 gpArgs->fPositionVar.set(SkSLType::kFloat2, "devcoord");
737
738 // Output local coordinates.
739 if (proc.fFlags & ProcessorFlags::kHasLocalCoords) {
740 // Do math in a way that preserves exact local coord boundaries when there is no local
741 // rotate and vertexpos is on an exact shape boundary.
742 v->codeAppend("float2 T = vertexpos * .5 + .5;");
743 v->codeAppend("float2 localcoord = localrect.xy * (1 - T) + "
744 "localrect.zw * T + "
745 "translate_and_localrotate.zw * T.yx;");
746 gpArgs->fLocalCoordVar.set(SkSLType::kFloat2, "localcoord");
747 }
748
749 // Setup interpolants for coverage.
750 GrGLSLVarying arcCoord(useHWDerivatives ? SkSLType::kFloat2 : SkSLType::kFloat4);
751 varyings->addVarying("arccoord", &arcCoord);
752 v->codeAppend("if (0 != is_linear_coverage) {");
753 // We are a non-corner piece: Set x=0 to indicate built-in coverage, and
754 // interpolate linear coverage across y.
755 v->codeAppendf( "%s.xy = float2(0, coverage * coverage_multiplier);",
756 arcCoord.vsOut());
757 v->codeAppend("} else {");
758 // Find the normalized arc coordinates for our corner ellipse.
759 // (i.e., the coordinate system where x^2 + y^2 == 1).
760 v->codeAppend( "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;");
761 // We are a corner piece: Interpolate the arc coordinates for coverage.
762 // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0
763 // instructs the fragment shader to use linear coverage).
764 v->codeAppendf( "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut());
765 if (!useHWDerivatives) {
766 // The gradient is order-1: Interpolate it across arccoord.zw.
767 v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
768 v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut());
769 }
770 v->codeAppend("}");
771
772 // Emit the fragment shader.
773 f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn());
774 f->codeAppendf("half coverage;");
775 f->codeAppendf("if (0 == x_plus_1) {");
776 f->codeAppendf( "coverage = half(y);"); // We are a non-arc pixel (linear coverage).
777 f->codeAppendf("} else {");
778 f->codeAppendf( "float fn = x_plus_1 * (x_plus_1 - 2);"); // fn = (x+1)*(x-1) = x^2-1
779 f->codeAppendf( "fn = fma(y,y, fn);"); // fn = x^2 + y^2 - 1
780 if (useHWDerivatives) {
781 f->codeAppendf("float fnwidth = fwidth(fn);");
782 } else {
783 // The gradient is interpolated across arccoord.zw.
784 f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn());
785 f->codeAppendf("float fnwidth = abs(gx) + abs(gy);");
786 }
787 f->codeAppendf( "coverage = .5 - half(fn/fnwidth);");
788 if (proc.fFlags & ProcessorFlags::kMSAAEnabled) {
789 // MSAA uses ramps larger than 1px, so we need to clamp in both branches.
790 f->codeAppendf("}");
791 }
792 f->codeAppendf("coverage = clamp(coverage, 0, 1);");
793 if (!(proc.fFlags & ProcessorFlags::kMSAAEnabled)) {
794 // When not using MSAA, we only need to clamp in the "arc" branch.
795 f->codeAppendf("}");
796 }
797 if (proc.fFlags & ProcessorFlags::kFakeNonAA) {
798 f->codeAppendf("coverage = (coverage >= .5) ? 1 : 0;");
799 }
800 f->codeAppendf("half4 %s = half4(coverage);", args.fOutputCoverage);
801 }
802 };
803
makeProgramImpl(const GrShaderCaps &) const804 std::unique_ptr<GrGeometryProcessor::ProgramImpl> FillRRectOpImpl::Processor::makeProgramImpl(
805 const GrShaderCaps&) const {
806 return std::make_unique<Impl>();
807 }
808
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && appliedClip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)809 void FillRRectOpImpl::onCreateProgramInfo(const GrCaps* caps,
810 SkArenaAlloc* arena,
811 const GrSurfaceProxyView& writeView,
812 bool usesMSAASurface,
813 GrAppliedClip&& appliedClip,
814 const GrDstProxyView& dstProxyView,
815 GrXferBarrierFlags renderPassXferBarriers,
816 GrLoadOp colorLoadOp) {
817 if (usesMSAASurface) {
818 fProcessorFlags |= ProcessorFlags::kMSAAEnabled;
819 }
820 GrGeometryProcessor* gp = Processor::Make(arena, fHelper.aaType(), fProcessorFlags);
821 fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface,
822 std::move(appliedClip), dstProxyView, gp,
823 GrPrimitiveType::kTriangles, renderPassXferBarriers,
824 colorLoadOp);
825 }
826
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)827 void FillRRectOpImpl::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
828 if (!fInstanceBuffer || !fIndexBuffer || !fVertexBuffer) {
829 return; // Setup failed.
830 }
831
832 flushState->bindPipelineAndScissorClip(*fProgramInfo, this->bounds());
833 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
834 flushState->bindBuffers(std::move(fIndexBuffer), std::move(fInstanceBuffer),
835 std::move(fVertexBuffer));
836 flushState->drawIndexedInstanced(std::size(kIndexData), 0, fInstanceCount, fBaseInstance, 0);
837 }
838
839 // Will the given corner look good if we use HW derivatives?
can_use_hw_derivatives_with_coverage(const skvx::float2 & devScale,const skvx::float2 & cornerRadii)840 bool can_use_hw_derivatives_with_coverage(const skvx::float2& devScale,
841 const skvx::float2& cornerRadii) {
842 skvx::float2 devRadii = devScale * cornerRadii;
843 if (devRadii[1] < devRadii[0]) {
844 devRadii = skvx::shuffle<1,0>(devRadii);
845 }
846 float minDevRadius = std::max(devRadii[0], 1.f); // Shader clamps radius at a minimum of 1.
847 // Is the gradient smooth enough for this corner look ok if we use hardware derivatives?
848 // This threshold was arrived at subjevtively on an NVIDIA chip.
849 return minDevRadius * minDevRadius * 5 > devRadii[1];
850 }
851
can_use_hw_derivatives_with_coverage(const skvx::float2 & devScale,const SkVector & cornerRadii)852 bool can_use_hw_derivatives_with_coverage(const skvx::float2& devScale,
853 const SkVector& cornerRadii) {
854 return can_use_hw_derivatives_with_coverage(devScale, skvx::float2::Load(&cornerRadii));
855 }
856
857 // Will the given round rect look good if we use HW derivatives?
can_use_hw_derivatives_with_coverage(const GrShaderCaps & shaderCaps,const SkMatrix & viewMatrix,const SkRRect & rrect)858 bool can_use_hw_derivatives_with_coverage(const GrShaderCaps& shaderCaps,
859 const SkMatrix& viewMatrix,
860 const SkRRect& rrect) {
861 if (!shaderCaps.fShaderDerivativeSupport) {
862 return false;
863 }
864
865 auto x = skvx::float2(viewMatrix.getScaleX(), viewMatrix.getSkewX());
866 auto y = skvx::float2(viewMatrix.getSkewY(), viewMatrix.getScaleY());
867 skvx::float2 devScale = sqrt(x*x + y*y);
868 switch (rrect.getType()) {
869 case SkRRect::kEmpty_Type:
870 case SkRRect::kRect_Type:
871 return true;
872
873 case SkRRect::kOval_Type:
874 case SkRRect::kSimple_Type:
875 return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii());
876
877 case SkRRect::kNinePatch_Type: {
878 skvx::float2 r0 = skvx::float2::Load(SkRRectPriv::GetRadiiArray(rrect));
879 skvx::float2 r1 = skvx::float2::Load(SkRRectPriv::GetRadiiArray(rrect) + 2);
880 skvx::float2 minRadii = min(r0, r1);
881 skvx::float2 maxRadii = max(r0, r1);
882 return can_use_hw_derivatives_with_coverage(devScale,
883 skvx::float2(minRadii[0], maxRadii[1])) &&
884 can_use_hw_derivatives_with_coverage(devScale,
885 skvx::float2(maxRadii[0], minRadii[1]));
886 }
887
888 case SkRRect::kComplex_Type: {
889 for (int i = 0; i < 4; ++i) {
890 auto corner = static_cast<SkRRect::Corner>(i);
891 if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) {
892 return false;
893 }
894 }
895 return true;
896 }
897 }
898 SK_ABORT("Invalid round rect type.");
899 }
900
901 } // anonymous namespace
902
Make(GrRecordingContext * ctx,SkArenaAlloc * arena,GrPaint && paint,const SkMatrix & viewMatrix,const SkRRect & rrect,const SkRect & localRect,GrAA aa)903 GrOp::Owner Make(GrRecordingContext* ctx,
904 SkArenaAlloc* arena,
905 GrPaint&& paint,
906 const SkMatrix& viewMatrix,
907 const SkRRect& rrect,
908 const SkRect& localRect,
909 GrAA aa) {
910 return FillRRectOpImpl::Make(ctx, arena, std::move(paint), viewMatrix, rrect, localRect, aa);
911 }
912
Make(GrRecordingContext * ctx,SkArenaAlloc * arena,GrPaint && paint,const SkMatrix & viewMatrix,const SkRRect & rrect,const SkMatrix & localMatrix,GrAA aa)913 GrOp::Owner Make(GrRecordingContext* ctx,
914 SkArenaAlloc* arena,
915 GrPaint&& paint,
916 const SkMatrix& viewMatrix,
917 const SkRRect& rrect,
918 const SkMatrix& localMatrix,
919 GrAA aa) {
920 return FillRRectOpImpl::Make(ctx, arena, std::move(paint), viewMatrix, rrect, localMatrix, aa);
921 }
922
923 } // namespace skgpu::v1::FillRRectOp
924
925 #if GR_TEST_UTILS
926
927 #include "src/gpu/ganesh/GrDrawOpTest.h"
928
GR_DRAW_OP_TEST_DEFINE(FillRRectOp)929 GR_DRAW_OP_TEST_DEFINE(FillRRectOp) {
930 SkArenaAlloc arena(64 * sizeof(float));
931 SkMatrix viewMatrix = GrTest::TestMatrix(random);
932 GrAA aa = GrAA(random->nextBool());
933
934 SkRect rect = GrTest::TestRect(random);
935 float w = rect.width();
936 float h = rect.height();
937
938 SkRRect rrect;
939 // TODO: test out other rrect configurations
940 rrect.setNinePatch(rect, w / 3.0f, h / 4.0f, w / 5.0f, h / 6.0);
941
942 return skgpu::v1::FillRRectOp::Make(context,
943 &arena,
944 std::move(paint),
945 viewMatrix,
946 rrect,
947 rrect.rect(),
948 aa);
949 }
950
951 #endif
952