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