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