• 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/GrFillRRectOp.h"
9 
10 #include "include/private/GrRecordingContext.h"
11 #include "src/core/SkRRectPriv.h"
12 #include "src/gpu/GrCaps.h"
13 #include "src/gpu/GrMemoryPool.h"
14 #include "src/gpu/GrOpFlushState.h"
15 #include "src/gpu/GrOpsRenderPass.h"
16 #include "src/gpu/GrProgramInfo.h"
17 #include "src/gpu/GrRecordingContextPriv.h"
18 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
19 #include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
20 #include "src/gpu/glsl/GrGLSLVarying.h"
21 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
22 
23 // Hardware derivatives are not always accurate enough for highly elliptical corners. This method
24 // checks to make sure the corners will still all look good if we use HW derivatives.
25 static bool can_use_hw_derivatives_with_coverage(
26         const GrShaderCaps&, const SkMatrix&, const SkRRect&);
27 
Make(GrRecordingContext * ctx,GrAAType aaType,const SkMatrix & viewMatrix,const SkRRect & rrect,const GrCaps & caps,GrPaint && paint)28 std::unique_ptr<GrFillRRectOp> GrFillRRectOp::Make(
29         GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect,
30         const GrCaps& caps, GrPaint&& paint) {
31     if (!caps.instanceAttribSupport()) {
32         return nullptr;
33     }
34 
35     Flags flags = Flags::kNone;
36     if (GrAAType::kCoverage == aaType) {
37         // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we
38         // already use HW derivatives. The only trick will be adjusting the AA outset to account for
39         // perspective. (i.e., outset = 0.5 * z.)
40         if (viewMatrix.hasPerspective()) {
41             return nullptr;
42         }
43         if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) {
44             // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms
45             // in coverage mode. We use them as long as the approximation will be accurate enough.
46             flags |= Flags::kUseHWDerivatives;
47         }
48     } else {
49         if (GrAAType::kMSAA == aaType) {
50             if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleMaskSupport() ||
51                 caps.shaderCaps()->canOnlyUseSampleMaskWithStencil()) {
52                 return nullptr;
53             }
54         }
55         if (viewMatrix.hasPerspective()) {
56             // HW derivatives are consistently slower on all platforms in sample mask mode. We
57             // therefore only use them when there is perspective, since then we can't interpolate
58             // the symbolic screen-space gradient.
59             flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective;
60         }
61     }
62 
63     // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space.
64     float l = rrect.rect().left(), r = rrect.rect().right(),
65           t = rrect.rect().top(), b = rrect.rect().bottom();
66     SkMatrix m;
67     // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b].
68     m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2);
69     // Map to device space.
70     m.postConcat(viewMatrix);
71 
72     SkRect devBounds;
73     if (!(flags & Flags::kHasPerspective)) {
74         // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's
75         // device-space quad, it's quite simple to find the bounding rectangle:
76         devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0);
77         devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()),
78                          SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY()));
79     } else {
80         viewMatrix.mapRect(&devBounds, rrect.rect());
81     }
82 
83     if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) {
84         // We are on a platform that prefers fine triangles instead of using the sample mask. See if
85         // the round rect is large enough that it will be faster for us to send it off to the
86         // default path renderer instead. The 200x200 threshold was arrived at using the
87         // "shapes_rrect" benchmark on an ARM Galaxy S9.
88         if (devBounds.height() * devBounds.width() > 200 * 200) {
89             return nullptr;
90         }
91     }
92 
93     GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
94     return pool->allocate<GrFillRRectOp>(aaType, rrect, flags, m, std::move(paint), devBounds);
95 }
96 
GrFillRRectOp(GrAAType aaType,const SkRRect & rrect,Flags flags,const SkMatrix & totalShapeMatrix,GrPaint && paint,const SkRect & devBounds)97 GrFillRRectOp::GrFillRRectOp(GrAAType aaType, const SkRRect& rrect, Flags flags,
98                              const SkMatrix& totalShapeMatrix, GrPaint&& paint,
99                              const SkRect& devBounds)
100         : GrDrawOp(ClassID())
101         , fAAType(aaType)
102         , fOriginalColor(paint.getColor4f())
103         , fLocalRect(rrect.rect())
104         , fFlags(flags)
105         , fProcessors(std::move(paint)) {
106     SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective());
107     this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsHairline::kNo);
108 
109     // Write the matrix attribs.
110     const SkMatrix& m = totalShapeMatrix;
111     if (!(fFlags & Flags::kHasPerspective)) {
112         // Affine 2D transformation (float2x2 plus float2 translate).
113         SkASSERT(!m.hasPerspective());
114         this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY());
115         this->writeInstanceData(m.getTranslateX(), m.getTranslateY());
116     } else {
117         // Perspective float3x3 transformation matrix.
118         SkASSERT(m.hasPerspective());
119         m.get9(this->appendInstanceData<float>(9));
120     }
121 
122     // Convert the radii to [-1, -1, +1, +1] space and write their attribs.
123     Sk4f radiiX, radiiY;
124     Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY);
125     (radiiX * (2/rrect.width())).store(this->appendInstanceData<float>(4));
126     (radiiY * (2/rrect.height())).store(this->appendInstanceData<float>(4));
127 
128     // We will write the color and local rect attribs during finalize().
129 }
130 
finalize(const GrCaps & caps,const GrAppliedClip * clip,bool hasMixedSampledCoverage,GrClampType clampType)131 GrProcessorSet::Analysis GrFillRRectOp::finalize(
132         const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
133         GrClampType clampType) {
134     SkASSERT(1 == fInstanceCount);
135 
136     SkPMColor4f overrideColor;
137     const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
138             fOriginalColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
139             &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType,
140             &overrideColor);
141 
142     // Finish writing the instance attribs.
143     SkPMColor4f finalColor = analysis.inputColorIsOverridden() ? overrideColor : fOriginalColor;
144     if (!SkPMColor4fFitsInBytes(finalColor)) {
145         fFlags |= Flags::kWideColor;
146         this->writeInstanceData(finalColor);
147     } else {
148         this->writeInstanceData(finalColor.toBytes_RGBA());
149     }
150 
151     if (analysis.usesLocalCoords()) {
152         this->writeInstanceData(fLocalRect);
153         fFlags |= Flags::kHasLocalCoords;
154     }
155     fInstanceStride = fInstanceData.count();
156 
157     return analysis;
158 }
159 
onCombineIfPossible(GrOp * op,GrRecordingContext::Arenas *,const GrCaps &)160 GrDrawOp::CombineResult GrFillRRectOp::onCombineIfPossible(GrOp* op, GrRecordingContext::Arenas*,
161                                                            const GrCaps&) {
162     const auto& that = *op->cast<GrFillRRectOp>();
163     if (fFlags != that.fFlags || fProcessors != that.fProcessors ||
164         fInstanceData.count() > std::numeric_limits<int>::max() - that.fInstanceData.count()) {
165         return CombineResult::kCannotCombine;
166     }
167 
168     fInstanceData.push_back_n(that.fInstanceData.count(), that.fInstanceData.begin());
169     fInstanceCount += that.fInstanceCount;
170     SkASSERT(fInstanceStride == that.fInstanceStride);
171     return CombineResult::kMerged;
172 }
173 
174 class GrFillRRectOp::Processor : public GrGeometryProcessor {
175 public:
Make(SkArenaAlloc * arena,GrAAType aaType,Flags flags)176     static GrGeometryProcessor* Make(SkArenaAlloc* arena, GrAAType aaType, Flags flags) {
177         return arena->make<Processor>(aaType, flags);
178     }
179 
name() const180     const char* name() const final { return "GrFillRRectOp::Processor"; }
181 
getGLSLProcessorKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const182     void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const final {
183         b->add32(((uint32_t)fFlags << 16) | (uint32_t)fAAType);
184     }
185 
186     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
187 
188 private:
189     friend class ::SkArenaAlloc; // for access to ctor
190 
Processor(GrAAType aaType,Flags flags)191     Processor(GrAAType aaType, Flags flags)
192             : INHERITED(kGrFillRRectOp_Processor_ClassID)
193             , fAAType(aaType)
194             , fFlags(flags) {
195         int numVertexAttribs = (GrAAType::kCoverage == fAAType) ? 3 : 2;
196         this->setVertexAttributes(kVertexAttribs, numVertexAttribs);
197 
198         if (!(flags & Flags::kHasPerspective)) {
199             // Affine 2D transformation (float2x2 plus float2 translate).
200             fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
201             fInstanceAttribs.emplace_back(
202                     "translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
203         } else {
204             // Perspective float3x3 transformation matrix.
205             fInstanceAttribs.emplace_back("persp_x", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
206             fInstanceAttribs.emplace_back("persp_y", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
207             fInstanceAttribs.emplace_back("persp_z", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
208         }
209         fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
210         fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
211         fColorAttrib = &fInstanceAttribs.push_back(
212                 MakeColorAttribute("color", (flags & Flags::kWideColor)));
213         if (fFlags & Flags::kHasLocalCoords) {
214             fInstanceAttribs.emplace_back(
215                     "local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
216         }
217         this->setInstanceAttributes(fInstanceAttribs.begin(), fInstanceAttribs.count());
218 
219         if (GrAAType::kMSAA == fAAType) {
220             this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
221         }
222     }
223 
224     static constexpr Attribute kVertexAttribs[] = {
225             {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
226             {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
227             // Coverage only.
228             {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
229 
230     const GrAAType fAAType;
231     const Flags fFlags;
232 
233     SkSTArray<6, Attribute> fInstanceAttribs;
234     const Attribute* fColorAttrib;
235 
236     class CoverageImpl;
237     class MSAAImpl;
238 
239     typedef GrGeometryProcessor INHERITED;
240 };
241 
242 constexpr GrPrimitiveProcessor::Attribute GrFillRRectOp::Processor::kVertexAttribs[];
243 
244 // Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear
245 // coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal
246 // edges. The Vertex struct tells the shader where to place its vertex within a normalized
247 // ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode.
248 struct CoverageVertex {
249     std::array<float, 4> fRadiiSelector;
250     std::array<float, 2> fCorner;
251     std::array<float, 2> fRadiusOutset;
252     std::array<float, 2> fAABloatDirection;
253     float fCoverage;
254     float fIsLinearCoverage;
255 };
256 
257 // This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices
258 // of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than
259 // rectangles.
260 static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2);
261 
262 static constexpr CoverageVertex kCoverageVertexData[] = {
263         // Left inset edge.
264         {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{+1,0}},  1,  1},
265         {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{+1,0}},  1,  1},
266 
267         // Top inset edge.
268         {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}},  {{0,+1}},  1,  1},
269         {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}},  {{0,+1}},  1,  1},
270 
271         // Right inset edge.
272         {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}},  {{-1,0}},  1,  1},
273         {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}},  {{-1,0}},  1,  1},
274 
275         // Bottom inset edge.
276         {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}},  {{0,-1}},  1,  1},
277         {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}},  {{0,-1}},  1,  1},
278 
279 
280         // Left outset edge.
281         {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{-1,0}},  0,  1},
282         {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{-1,0}},  0,  1},
283 
284         // Top outset edge.
285         {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}},  {{0,-1}},  0,  1},
286         {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}},  {{0,-1}},  0,  1},
287 
288         // Right outset edge.
289         {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}},  {{+1,0}},  0,  1},
290         {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}},  {{+1,0}},  0,  1},
291 
292         // Bottom outset edge.
293         {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}},  {{0,+1}},  0,  1},
294         {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}},  {{0,+1}},  0,  1},
295 
296 
297         // Top-left corner.
298         {{{1,0,0,0}},  {{-1,-1}},  {{ 0,+1}},  {{-1, 0}},  0,  0},
299         {{{1,0,0,0}},  {{-1,-1}},  {{ 0,+1}},  {{+1, 0}},  1,  0},
300         {{{1,0,0,0}},  {{-1,-1}},  {{+1, 0}},  {{ 0,+1}},  1,  0},
301         {{{1,0,0,0}},  {{-1,-1}},  {{+1, 0}},  {{ 0,-1}},  0,  0},
302         {{{1,0,0,0}},  {{-1,-1}},  {{+kOctoOffset,0}},  {{-1,-1}},  0,  0},
303         {{{1,0,0,0}},  {{-1,-1}},  {{0,+kOctoOffset}},  {{-1,-1}},  0,  0},
304 
305         // Top-right corner.
306         {{{0,1,0,0}},  {{+1,-1}},  {{-1, 0}},  {{ 0,-1}},  0,  0},
307         {{{0,1,0,0}},  {{+1,-1}},  {{-1, 0}},  {{ 0,+1}},  1,  0},
308         {{{0,1,0,0}},  {{+1,-1}},  {{ 0,+1}},  {{-1, 0}},  1,  0},
309         {{{0,1,0,0}},  {{+1,-1}},  {{ 0,+1}},  {{+1, 0}},  0,  0},
310         {{{0,1,0,0}},  {{+1,-1}},  {{0,+kOctoOffset}},  {{+1,-1}},  0,  0},
311         {{{0,1,0,0}},  {{+1,-1}},  {{-kOctoOffset,0}},  {{+1,-1}},  0,  0},
312 
313         // Bottom-right corner.
314         {{{0,0,1,0}},  {{+1,+1}},  {{ 0,-1}},  {{+1, 0}},  0,  0},
315         {{{0,0,1,0}},  {{+1,+1}},  {{ 0,-1}},  {{-1, 0}},  1,  0},
316         {{{0,0,1,0}},  {{+1,+1}},  {{-1, 0}},  {{ 0,-1}},  1,  0},
317         {{{0,0,1,0}},  {{+1,+1}},  {{-1, 0}},  {{ 0,+1}},  0,  0},
318         {{{0,0,1,0}},  {{+1,+1}},  {{-kOctoOffset,0}},  {{+1,+1}},  0,  0},
319         {{{0,0,1,0}},  {{+1,+1}},  {{0,-kOctoOffset}},  {{+1,+1}},  0,  0},
320 
321         // Bottom-left corner.
322         {{{0,0,0,1}},  {{-1,+1}},  {{+1, 0}},  {{ 0,+1}},  0,  0},
323         {{{0,0,0,1}},  {{-1,+1}},  {{+1, 0}},  {{ 0,-1}},  1,  0},
324         {{{0,0,0,1}},  {{-1,+1}},  {{ 0,-1}},  {{+1, 0}},  1,  0},
325         {{{0,0,0,1}},  {{-1,+1}},  {{ 0,-1}},  {{-1, 0}},  0,  0},
326         {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}},  {{-1,+1}},  0,  0},
327         {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}},  {{-1,+1}},  0,  0}};
328 
329 GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey);
330 
331 static constexpr uint16_t kCoverageIndexData[] = {
332         // Inset octagon (solid coverage).
333         0, 1, 7,
334         1, 2, 7,
335         7, 2, 6,
336         2, 3, 6,
337         6, 3, 5,
338         3, 4, 5,
339 
340         // AA borders (linear coverage).
341         0, 1, 8, 1, 9, 8,
342         2, 3, 10, 3, 11, 10,
343         4, 5, 12, 5, 13, 12,
344         6, 7, 14, 7, 15, 14,
345 
346         // Top-left arc.
347         16, 17, 21,
348         17, 21, 18,
349         21, 18, 20,
350         18, 20, 19,
351 
352         // Top-right arc.
353         22, 23, 27,
354         23, 27, 24,
355         27, 24, 26,
356         24, 26, 25,
357 
358         // Bottom-right arc.
359         28, 29, 33,
360         29, 33, 30,
361         33, 30, 32,
362         30, 32, 31,
363 
364         // Bottom-left arc.
365         34, 35, 39,
366         35, 39, 36,
367         39, 36, 38,
368         36, 38, 37};
369 
370 GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey);
371 
372 
373 // Our MSAA geometry consists of an inset octagon with full sample mask coverage, circumscribed
374 // by a larger octagon that modifies the sample mask for the arc at each corresponding corner.
375 struct MSAAVertex {
376     std::array<float, 4> fRadiiSelector;
377     std::array<float, 2> fCorner;
378     std::array<float, 2> fRadiusOutset;
379 };
380 
381 static constexpr MSAAVertex kMSAAVertexData[] = {
382         // Left edge. (Negative radii selector indicates this is not an arc section.)
383         {{{0,0,0,-1}},  {{-1,+1}},  {{0,-1}}},
384         {{{-1,0,0,0}},  {{-1,-1}},  {{0,+1}}},
385 
386         // Top edge.
387         {{{-1,0,0,0}},  {{-1,-1}},  {{+1,0}}},
388         {{{0,-1,0,0}},  {{+1,-1}},  {{-1,0}}},
389 
390         // Right edge.
391         {{{0,-1,0,0}},  {{+1,-1}},  {{0,+1}}},
392         {{{0,0,-1,0}},  {{+1,+1}},  {{0,-1}}},
393 
394         // Bottom edge.
395         {{{0,0,-1,0}},  {{+1,+1}},  {{-1,0}}},
396         {{{0,0,0,-1}},  {{-1,+1}},  {{+1,0}}},
397 
398         // Top-left corner.
399         {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}}},
400         {{{1,0,0,0}},  {{-1,-1}},  {{0,+kOctoOffset}}},
401         {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}}},
402         {{{1,0,0,0}},  {{-1,-1}},  {{+kOctoOffset,0}}},
403 
404         // Top-right corner.
405         {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}}},
406         {{{0,1,0,0}},  {{+1,-1}},  {{-kOctoOffset,0}}},
407         {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}}},
408         {{{0,1,0,0}},  {{+1,-1}},  {{0,+kOctoOffset}}},
409 
410         // Bottom-right corner.
411         {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}}},
412         {{{0,0,1,0}},  {{+1,+1}},  {{0,-kOctoOffset}}},
413         {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}}},
414         {{{0,0,1,0}},  {{+1,+1}},  {{-kOctoOffset,0}}},
415 
416         // Bottom-left corner.
417         {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}}},
418         {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}}},
419         {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}}},
420         {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}}}};
421 
422 GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey);
423 
424 static constexpr uint16_t kMSAAIndexData[] = {
425         // Inset octagon. (Full sample mask.)
426         0, 1, 2,
427         0, 2, 3,
428         0, 3, 6,
429         3, 4, 5,
430         3, 5, 6,
431         6, 7, 0,
432 
433         // Top-left arc. (Sample mask is set to the arc.)
434          8,  9, 10,
435          9, 11, 10,
436 
437         // Top-right arc.
438         12, 13, 14,
439         13, 15, 14,
440 
441         // Bottom-right arc.
442         16, 17, 18,
443         17, 19, 18,
444 
445         // Bottom-left arc.
446         20, 21, 22,
447         21, 23, 22};
448 
449 GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey);
450 
onPrePrepare(GrRecordingContext * context,const GrSurfaceProxyView * dstView,GrAppliedClip * clip,const GrXferProcessor::DstProxyView & dstProxyView)451 void GrFillRRectOp::onPrePrepare(GrRecordingContext* context,
452                                  const GrSurfaceProxyView* dstView,
453                                  GrAppliedClip* clip,
454                                  const GrXferProcessor::DstProxyView& dstProxyView) {
455     SkArenaAlloc* arena = context->priv().recordTimeAllocator();
456 
457     // This is equivalent to a GrOpFlushState::detachAppliedClip
458     GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip();
459 
460     // TODO: it would be cool if, right here, we created both the program info and desc
461     // in the record-time arena. Then, if the program info had already been seen, we could
462     // get pointers back to the prior versions and be able to return the allocated space
463     // back to the arena.
464     fProgramInfo = this->createProgramInfo(context->priv().caps(), arena, dstView,
465                                            std::move(appliedClip), dstProxyView);
466 
467     context->priv().recordProgramInfo(fProgramInfo);
468 }
469 
onPrepare(GrOpFlushState * flushState)470 void GrFillRRectOp::onPrepare(GrOpFlushState* flushState) {
471     if (void* instanceData = flushState->makeVertexSpace(fInstanceStride, fInstanceCount,
472                                                          &fInstanceBuffer, &fBaseInstance)) {
473         SkASSERT(fInstanceStride * fInstanceCount == fInstanceData.count());
474         memcpy(instanceData, fInstanceData.begin(), fInstanceData.count());
475     }
476 
477     if (GrAAType::kCoverage == fAAType) {
478         GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey);
479 
480         fIndexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
481                 GrGpuBufferType::kIndex, sizeof(kCoverageIndexData), kCoverageIndexData,
482                 gCoverageIndexBufferKey);
483 
484         GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey);
485 
486         fVertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
487                 GrGpuBufferType::kVertex, sizeof(kCoverageVertexData), kCoverageVertexData,
488                 gCoverageVertexBufferKey);
489 
490         fIndexCount = SK_ARRAY_COUNT(kCoverageIndexData);
491     } else {
492         GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey);
493 
494         fIndexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
495                 GrGpuBufferType::kIndex, sizeof(kMSAAIndexData), kMSAAIndexData,
496                 gMSAAIndexBufferKey);
497 
498         GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey);
499 
500         fVertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
501                 GrGpuBufferType::kVertex, sizeof(kMSAAVertexData), kMSAAVertexData,
502                 gMSAAVertexBufferKey);
503 
504         fIndexCount = SK_ARRAY_COUNT(kMSAAIndexData);
505     }
506 }
507 
508 class GrFillRRectOp::Processor::CoverageImpl : public GrGLSLGeometryProcessor {
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)509     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
510         const auto& proc = args.fGP.cast<Processor>();
511         bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
512 
513         SkASSERT(proc.vertexStride() == sizeof(CoverageVertex));
514 
515         GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
516         varyings->emitAttributes(proc);
517         varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor,
518                                           GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
519 
520         // Emit the vertex shader.
521         GrGLSLVertexBuilder* v = args.fVertBuilder;
522 
523         // Unpack vertex attribs.
524         v->codeAppend("float2 corner = corner_and_radius_outsets.xy;");
525         v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;");
526         v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;");
527         v->codeAppend("float coverage = aa_bloat_and_coverage.z;");
528         v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;");
529 
530         // Find the amount to bloat each edge for AA (in source space).
531         v->codeAppend("float2 pixellength = inversesqrt("
532                               "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));");
533         v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;");
534         v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + "
535                                            "abs(normalized_axis_dirs.zw));");
536         v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;");
537 
538         // Identify our radii.
539         v->codeAppend("float4 radii_and_neighbors = radii_selector"
540                               "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);");
541         v->codeAppend("float2 radii = radii_and_neighbors.xy;");
542         v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;");
543 
544         v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {");
545                           // The rrect is more narrow than an AA coverage ramp. We can't draw as-is
546                           // or else opposite AA borders will overlap. Instead, fudge the size up to
547                           // the width of a coverage ramp, and then reduce total coverage to make
548                           // the rect appear more thin.
549         v->codeAppend(    "corner = max(abs(corner), aa_bloatradius) * sign(corner);");
550         v->codeAppend(    "coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1);");
551                           // Set radii to zero to ensure we take the "linear coverage" codepath.
552                           // (The "coverage" variable only has effect in the linear codepath.)
553         v->codeAppend(    "radii = float2(0);");
554         v->codeAppend("}");
555 
556         v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.25))) {");
557                           // The radii are very small. Demote this arc to a sharp 90 degree corner.
558         v->codeAppend(    "radii = aa_bloatradius;");
559                           // Snap octagon vertices to the corner of the bounding box.
560         v->codeAppend(    "radius_outset = floor(abs(radius_outset)) * radius_outset;");
561         v->codeAppend(    "is_linear_coverage = 1;");
562         v->codeAppend("} else {");
563                           // Don't let radii get smaller than a pixel.
564         v->codeAppend(    "radii = clamp(radii, pixellength, 2 - pixellength);");
565         v->codeAppend(    "neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength);");
566                           // Don't let neighboring radii get closer together than 1/16 pixel.
567         v->codeAppend(    "float2 spacing = 2 - radii - neighbor_radii;");
568         v->codeAppend(    "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));");
569         v->codeAppend(    "radii -= extra_pad * .5;");
570         v->codeAppend("}");
571 
572         // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in
573         // normalized [-1,-1,+1,+1] space.
574         v->codeAppend("float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;");
575         v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;");
576 
577         // Emit transforms.
578         GrShaderVar localCoord("", kFloat2_GrSLType);
579         if (proc.fFlags & Flags::kHasLocalCoords) {
580             v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
581                                                "local_rect.zw * (1 + vertexpos)) * .5;");
582             localCoord.set(kFloat2_GrSLType, "localcoord");
583         }
584         this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
585                              args.fFPCoordTransformHandler);
586 
587         // Transform to device space.
588         SkASSERT(!(proc.fFlags & Flags::kHasPerspective));
589         v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
590         v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
591         gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
592 
593         // Setup interpolants for coverage.
594         GrGLSLVarying arcCoord(useHWDerivatives ? kFloat2_GrSLType : kFloat4_GrSLType);
595         varyings->addVarying("arccoord", &arcCoord);
596         v->codeAppend("if (0 != is_linear_coverage) {");
597                            // We are a non-corner piece: Set x=0 to indicate built-in coverage, and
598                            // interpolate linear coverage across y.
599         v->codeAppendf(    "%s.xy = float2(0, coverage);", arcCoord.vsOut());
600         v->codeAppend("} else {");
601                            // Find the normalized arc coordinates for our corner ellipse.
602                            // (i.e., the coordinate system where x^2 + y^2 == 1).
603         v->codeAppend(    "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;");
604                            // We are a corner piece: Interpolate the arc coordinates for coverage.
605                            // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0
606                            // instructs the fragment shader to use linear coverage).
607         v->codeAppendf(    "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut());
608         if (!useHWDerivatives) {
609             // The gradient is order-1: Interpolate it across arccoord.zw.
610             v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
611             v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut());
612         }
613         v->codeAppend("}");
614 
615         // Emit the fragment shader.
616         GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
617 
618         f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn());
619         f->codeAppendf("half coverage;");
620         f->codeAppendf("if (0 == x_plus_1) {");
621         f->codeAppendf(    "coverage = half(y);");  // We are a non-arc pixel (linear coverage).
622         f->codeAppendf("} else {");
623         f->codeAppendf(    "float fn = x_plus_1 * (x_plus_1 - 2);");  // fn = (x+1)*(x-1) = x^2-1
624         f->codeAppendf(    "fn = fma(y,y, fn);");  // fn = x^2 + y^2 - 1
625         if (useHWDerivatives) {
626             f->codeAppendf("float fnwidth = fwidth(fn);");
627         } else {
628             // The gradient is interpolated across arccoord.zw.
629             f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn());
630             f->codeAppendf("float fnwidth = abs(gx) + abs(gy);");
631         }
632         f->codeAppendf(    "half d = half(fn/fnwidth);");
633         f->codeAppendf(    "coverage = clamp(.5 - d, 0, 1);");
634         f->codeAppendf("}");
635         f->codeAppendf("%s = half4(coverage);", args.fOutputCoverage);
636     }
637 
setData(const GrGLSLProgramDataManager & pdman,const GrPrimitiveProcessor &,const CoordTransformRange & transformRange)638     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
639                  const CoordTransformRange& transformRange) override {
640         this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
641     }
642 };
643 
644 
645 class GrFillRRectOp::Processor::MSAAImpl : public GrGLSLGeometryProcessor {
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)646     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
647         const auto& proc = args.fGP.cast<Processor>();
648         bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
649         bool hasPerspective = (proc.fFlags & Flags::kHasPerspective);
650         bool hasLocalCoords = (proc.fFlags & Flags::kHasLocalCoords);
651         SkASSERT(useHWDerivatives == hasPerspective);
652 
653         SkASSERT(proc.vertexStride() == sizeof(MSAAVertex));
654 
655         // Emit the vertex shader.
656         GrGLSLVertexBuilder* v = args.fVertBuilder;
657 
658         GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
659         varyings->emitAttributes(proc);
660         varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor,
661                                           GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
662 
663         // Unpack vertex attribs.
664         v->codeAppendf("float2 corner = corner_and_radius_outsets.xy;");
665         v->codeAppendf("float2 radius_outset = corner_and_radius_outsets.zw;");
666 
667         // Identify our radii.
668         v->codeAppend("float2 radii;");
669         v->codeAppend("radii.x = dot(radii_selector, radii_x);");
670         v->codeAppend("radii.y = dot(radii_selector, radii_y);");
671         v->codeAppendf("bool is_arc_section = (radii.x > 0);");
672         v->codeAppendf("radii = abs(radii);");
673 
674         // Find our vertex position, adjusted for radii. Our rect is drawn in normalized
675         // [-1,-1,+1,+1] space.
676         v->codeAppend("float2 vertexpos = corner + radius_outset * radii;");
677 
678         // Emit transforms.
679         GrShaderVar localCoord("", kFloat2_GrSLType);
680         if (hasLocalCoords) {
681             v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
682                                                "local_rect.zw * (1 + vertexpos)) * .5;");
683             localCoord.set(kFloat2_GrSLType, "localcoord");
684         }
685         this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
686                              args.fFPCoordTransformHandler);
687 
688         // Transform to device space.
689         if (!hasPerspective) {
690             v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
691             v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
692             gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
693         } else {
694             v->codeAppend("float3x3 persp_matrix = float3x3(persp_x, persp_y, persp_z);");
695             v->codeAppend("float3 devcoord = float3(vertexpos, 1) * persp_matrix;");
696             gpArgs->fPositionVar.set(kFloat3_GrSLType, "devcoord");
697         }
698 
699         // Determine normalized arc coordinates for the implicit function.
700         GrGLSLVarying arcCoord((useHWDerivatives) ? kFloat2_GrSLType : kFloat4_GrSLType);
701         varyings->addVarying("arccoord", &arcCoord);
702         v->codeAppendf("if (is_arc_section) {");
703         v->codeAppendf(    "%s.xy = 1 - abs(radius_outset);", arcCoord.vsOut());
704         if (!useHWDerivatives) {
705             // The gradient is order-1: Interpolate it across arccoord.zw.
706             // This doesn't work with perspective.
707             SkASSERT(!hasPerspective);
708             v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
709             v->codeAppendf("%s.zw = derivatives * (%s.xy/radii * corner * 2);",
710                            arcCoord.vsOut(), arcCoord.vsOut());
711         }
712         v->codeAppendf("} else {");
713         if (useHWDerivatives) {
714             v->codeAppendf("%s = float2(0);", arcCoord.vsOut());
715         } else {
716             v->codeAppendf("%s = float4(0);", arcCoord.vsOut());
717         }
718         v->codeAppendf("}");
719 
720         // Emit the fragment shader.
721         GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
722 
723         f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
724 
725         // If x,y == 0, then we are drawing a triangle that does not track an arc.
726         f->codeAppendf("if (float2(0) != %s.xy) {", arcCoord.fsIn());
727         f->codeAppendf(    "float fn = dot(%s.xy, %s.xy) - 1;", arcCoord.fsIn(), arcCoord.fsIn());
728         if (GrAAType::kMSAA == proc.fAAType) {
729             using ScopeFlags = GrGLSLFPFragmentBuilder::ScopeFlags;
730             if (!useHWDerivatives) {
731                 f->codeAppendf("float2 grad = %s.zw;", arcCoord.fsIn());
732                 f->applyFnToMultisampleMask("fn", "grad", ScopeFlags::kInsidePerPrimitiveBranch);
733             } else {
734                 f->applyFnToMultisampleMask("fn", nullptr, ScopeFlags::kInsidePerPrimitiveBranch);
735             }
736         } else {
737             f->codeAppendf("if (fn > 0) {");
738             f->codeAppendf(    "%s = half4(0);", args.fOutputCoverage);
739             f->codeAppendf("}");
740         }
741         f->codeAppendf("}");
742     }
743 
setData(const GrGLSLProgramDataManager & pdman,const GrPrimitiveProcessor &,const CoordTransformRange & transformRange)744     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
745                  const CoordTransformRange& transformRange) override {
746         this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
747     }
748 };
749 
createGLSLInstance(const GrShaderCaps &) const750 GrGLSLPrimitiveProcessor* GrFillRRectOp::Processor::createGLSLInstance(
751         const GrShaderCaps&) const {
752     if (GrAAType::kCoverage != fAAType) {
753         return new MSAAImpl();
754     }
755     return new CoverageImpl();
756 }
757 
createProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView * dstView,GrAppliedClip && appliedClip,const GrXferProcessor::DstProxyView & dstProxyView)758 GrProgramInfo* GrFillRRectOp::createProgramInfo(const GrCaps* caps,
759                                                 SkArenaAlloc* arena,
760                                                 const GrSurfaceProxyView* dstView,
761                                                 GrAppliedClip&& appliedClip,
762                                                 const GrXferProcessor::DstProxyView& dstProxyView) {
763     GrGeometryProcessor* geomProc = Processor::Make(arena, fAAType, fFlags);
764     SkASSERT(geomProc->instanceStride() == (size_t)fInstanceStride);
765 
766     GrPipeline::InitArgs initArgs;
767     if (GrAAType::kMSAA == fAAType) {
768         initArgs.fInputFlags = GrPipeline::InputFlags::kHWAntialias;
769     }
770     initArgs.fCaps = caps;
771     initArgs.fDstProxyView = dstProxyView;
772     initArgs.fOutputSwizzle = dstView->swizzle();
773 
774     GrPipeline::FixedDynamicState* fixedDynamicState = nullptr;
775 
776     if (appliedClip.scissorState().enabled()) {
777         fixedDynamicState = arena->make<GrPipeline::FixedDynamicState>(
778                                                         appliedClip.scissorState().rect());
779     }
780 
781     GrPipeline* pipeline = arena->make<GrPipeline>(initArgs,
782                                                    std::move(fProcessors),
783                                                    std::move(appliedClip));
784 
785     GrRenderTargetProxy* dstProxy = dstView->asRenderTargetProxy();
786     return arena->make<GrProgramInfo>(dstProxy->numSamples(),
787                                       dstProxy->numStencilSamples(),
788                                       dstProxy->backendFormat(),
789                                       dstView->origin(),
790                                       pipeline,
791                                       geomProc,
792                                       fixedDynamicState,
793                                       nullptr, 0,
794                                       GrPrimitiveType::kTriangles);
795 }
796 
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)797 void GrFillRRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
798     if (!fInstanceBuffer || !fIndexBuffer || !fVertexBuffer) {
799         return;  // Setup failed.
800     }
801 
802     if (!fProgramInfo) {
803         const GrSurfaceProxyView* dstView = flushState->view();
804 
805         fProgramInfo = this->createProgramInfo(&flushState->caps(),
806                                                flushState->allocator(),
807                                                dstView,
808                                                flushState->detachAppliedClip(),
809                                                flushState->dstProxyView());
810     }
811 
812     GrMesh* mesh = flushState->allocator()->make<GrMesh>();
813     mesh->setIndexedInstanced(std::move(fIndexBuffer), fIndexCount,
814                               std::move(fInstanceBuffer), fInstanceCount,
815                               fBaseInstance, GrPrimitiveRestart::kNo);
816     mesh->setVertexData(std::move(fVertexBuffer));
817 
818     flushState->opsRenderPass()->bindPipeline(*fProgramInfo, this->bounds());
819     flushState->opsRenderPass()->drawMeshes(*fProgramInfo, mesh, 1);
820 }
821 
822 // Will the given corner look good if we use HW derivatives?
can_use_hw_derivatives_with_coverage(const Sk2f & devScale,const Sk2f & cornerRadii)823 static bool can_use_hw_derivatives_with_coverage(const Sk2f& devScale, const Sk2f& cornerRadii) {
824     Sk2f devRadii = devScale * cornerRadii;
825     if (devRadii[1] < devRadii[0]) {
826         devRadii = SkNx_shuffle<1,0>(devRadii);
827     }
828     float minDevRadius = std::max(devRadii[0], 1.f);  // Shader clamps radius at a minimum of 1.
829     // Is the gradient smooth enough for this corner look ok if we use hardware derivatives?
830     // This threshold was arrived at subjevtively on an NVIDIA chip.
831     return minDevRadius * minDevRadius * 5 > devRadii[1];
832 }
833 
can_use_hw_derivatives_with_coverage(const Sk2f & devScale,const SkVector & cornerRadii)834 static bool can_use_hw_derivatives_with_coverage(
835         const Sk2f& devScale, const SkVector& cornerRadii) {
836     return can_use_hw_derivatives_with_coverage(devScale, Sk2f::Load(&cornerRadii));
837 }
838 
839 // 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)840 static bool can_use_hw_derivatives_with_coverage(
841         const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, const SkRRect& rrect) {
842     if (!shaderCaps.shaderDerivativeSupport()) {
843         return false;
844     }
845 
846     Sk2f x = Sk2f(viewMatrix.getScaleX(), viewMatrix.getSkewX());
847     Sk2f y = Sk2f(viewMatrix.getSkewY(), viewMatrix.getScaleY());
848     Sk2f devScale = (x*x + y*y).sqrt();
849     switch (rrect.getType()) {
850         case SkRRect::kEmpty_Type:
851         case SkRRect::kRect_Type:
852             return true;
853 
854         case SkRRect::kOval_Type:
855         case SkRRect::kSimple_Type:
856             return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii());
857 
858         case SkRRect::kNinePatch_Type: {
859             Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect));
860             Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2);
861             Sk2f minRadii = Sk2f::Min(r0, r1);
862             Sk2f maxRadii = Sk2f::Max(r0, r1);
863             return can_use_hw_derivatives_with_coverage(devScale, Sk2f(minRadii[0], maxRadii[1])) &&
864                    can_use_hw_derivatives_with_coverage(devScale, Sk2f(maxRadii[0], minRadii[1]));
865         }
866 
867         case SkRRect::kComplex_Type: {
868             for (int i = 0; i < 4; ++i) {
869                 auto corner = static_cast<SkRRect::Corner>(i);
870                 if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) {
871                     return false;
872                 }
873             }
874             return true;
875         }
876     }
877     SK_ABORT("Invalid round rect type.");
878 }
879