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