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