• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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