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