Coverage Report

Created: 2024-05-20 07:14

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