Coverage Report

Created: 2021-08-22 09:07

/src/skia/src/gpu/ops/StrokeTessellateOp.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2020 Google LLC.
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/StrokeTessellateOp.h"
9
10
#include "src/core/SkPathPriv.h"
11
#include "src/gpu/GrAppliedClip.h"
12
#include "src/gpu/GrOpFlushState.h"
13
#include "src/gpu/GrRecordingContextPriv.h"
14
#include "src/gpu/tessellate/GrStrokeFixedCountTessellator.h"
15
#include "src/gpu/tessellate/GrStrokeHardwareTessellator.h"
16
#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
17
18
using DynamicStroke = GrStrokeTessellationShader::DynamicStroke;
19
20
GrStrokeTessellateOp::GrStrokeTessellateOp(GrAAType aaType, const SkMatrix& viewMatrix,
21
                                           const SkPath& path, const SkStrokeRec& stroke,
22
                                           GrPaint&& paint)
23
        : GrDrawOp(ClassID())
24
        , fAAType(aaType)
25
        , fViewMatrix(viewMatrix)
26
        , fPathStrokeList(path, stroke, paint.getColor4f())
27
        , fTotalCombinedVerbCnt(path.countVerbs())
28
0
        , fProcessors(std::move(paint)) {
29
0
    if (!this->headColor().fitsInBytes()) {
30
0
        fShaderFlags |= ShaderFlags::kWideColor;
31
0
    }
32
0
    SkRect devBounds = path.getBounds();
33
0
    if (!this->headStroke().isHairlineStyle()) {
34
        // Non-hairlines inflate in local path space (pre-transform).
35
0
        fInflationRadius = stroke.getInflationRadius();
36
0
        devBounds.outset(fInflationRadius, fInflationRadius);
37
0
    }
38
0
    viewMatrix.mapRect(&devBounds, devBounds);
39
0
    if (this->headStroke().isHairlineStyle()) {
40
        // Hairlines inflate in device space (post-transform).
41
0
        fInflationRadius = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(),
42
0
                                                           stroke.getCap(), 1);
43
0
        devBounds.outset(fInflationRadius, fInflationRadius);
44
0
    }
45
0
    this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
46
0
}
47
48
0
void GrStrokeTessellateOp::visitProxies(const GrVisitProxyFunc& func) const {
49
0
    if (fFillProgram) {
50
0
        fFillProgram->visitFPProxies(func);
51
0
    } else if (fStencilProgram) {
52
0
        fStencilProgram->visitFPProxies(func);
53
0
    } else {
54
0
        fProcessors.visitProxies(func);
55
0
    }
56
0
}
57
58
GrProcessorSet::Analysis GrStrokeTessellateOp::finalize(const GrCaps& caps,
59
                                                        const GrAppliedClip* clip,
60
0
                                                        GrClampType clampType) {
61
    // Make sure the finalize happens before combining. We might change fNeedsStencil here.
62
0
    SkASSERT(fPathStrokeList.fNext == nullptr);
63
0
    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
64
0
            this->headColor(), GrProcessorAnalysisCoverage::kNone, clip,
65
0
            &GrUserStencilSettings::kUnused, caps, clampType, &this->headColor());
66
0
    fNeedsStencil = !analysis.unaffectedByDstValue();
67
0
    return analysis;
68
0
}
Unexecuted instantiation: GrStrokeTessellateOp::finalize(GrCaps const&, GrAppliedClip const*, GrClampType)
Unexecuted instantiation: GrStrokeTessellateOp::finalize(GrCaps const&, GrAppliedClip const*, GrClampType)
69
70
GrOp::CombineResult GrStrokeTessellateOp::onCombineIfPossible(GrOp* grOp, SkArenaAlloc* alloc,
71
0
                                                              const GrCaps& caps) {
72
0
    SkASSERT(grOp->classID() == this->classID());
73
0
    auto* op = static_cast<GrStrokeTessellateOp*>(grOp);
74
75
    // This must be called after finalize(). fNeedsStencil can change in finalize().
76
0
    SkASSERT(fProcessors.isFinalized());
77
0
    SkASSERT(op->fProcessors.isFinalized());
78
79
0
    if (fNeedsStencil ||
80
0
        op->fNeedsStencil ||
81
0
        fViewMatrix != op->fViewMatrix ||
82
0
        fAAType != op->fAAType ||
83
0
        fProcessors != op->fProcessors ||
84
0
        this->headStroke().isHairlineStyle() != op->headStroke().isHairlineStyle()) {
85
0
        return CombineResult::kCannotCombine;
86
0
    }
87
88
0
    auto combinedFlags = fShaderFlags | op->fShaderFlags;
89
0
    if (!(combinedFlags & ShaderFlags::kDynamicStroke) &&
90
0
        !DynamicStroke::StrokesHaveEqualDynamicState(this->headStroke(), op->headStroke())) {
91
        // The paths have different stroke properties. We will need to enable dynamic stroke if we
92
        // still decide to combine them.
93
0
        if (this->headStroke().isHairlineStyle()) {
94
0
            return CombineResult::kCannotCombine;  // Dynamic hairlines aren't supported.
95
0
        }
96
0
        combinedFlags |= ShaderFlags::kDynamicStroke;
97
0
    }
98
0
    if (!(combinedFlags & ShaderFlags::kDynamicColor) && this->headColor() != op->headColor()) {
99
        // The paths have different colors. We will need to enable dynamic color if we still decide
100
        // to combine them.
101
0
        combinedFlags |= ShaderFlags::kDynamicColor;
102
0
    }
103
104
    // Don't actually enable new dynamic state on ops that already have lots of verbs.
105
0
    constexpr static GrTFlagsMask<ShaderFlags> kDynamicStatesMask(ShaderFlags::kDynamicStroke |
106
0
                                                                  ShaderFlags::kDynamicColor);
107
0
    ShaderFlags neededDynamicStates = combinedFlags & kDynamicStatesMask;
108
0
    if (neededDynamicStates != ShaderFlags::kNone) {
109
0
        if (!this->shouldUseDynamicStates(neededDynamicStates) ||
110
0
            !op->shouldUseDynamicStates(neededDynamicStates)) {
111
0
            return CombineResult::kCannotCombine;
112
0
        }
113
0
    }
114
115
0
    fShaderFlags = combinedFlags;
116
117
    // Concat the op's PathStrokeList. Since the head element is allocated inside the op, we need to
118
    // copy it.
119
0
    auto* headCopy = alloc->make<PathStrokeList>(std::move(op->fPathStrokeList));
120
0
    *fPathStrokeTail = headCopy;
121
0
    fPathStrokeTail = (op->fPathStrokeTail == &op->fPathStrokeList.fNext) ? &headCopy->fNext
122
0
                                                                          : op->fPathStrokeTail;
123
124
0
    fInflationRadius = std::max(fInflationRadius, op->fInflationRadius);
125
0
    fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
126
0
    return CombineResult::kMerged;
127
0
}
Unexecuted instantiation: GrStrokeTessellateOp::onCombineIfPossible(GrOp*, SkArenaAlloc*, GrCaps const&)
Unexecuted instantiation: GrStrokeTessellateOp::onCombineIfPossible(GrOp*, SkArenaAlloc*, GrCaps const&)
128
129
// Marks every stencil value as "1".
130
constexpr static GrUserStencilSettings kMarkStencil(
131
    GrUserStencilSettings::StaticInit<
132
        0x0001,
133
        GrUserStencilTest::kLessIfInClip,  // Match kTestAndResetStencil.
134
        0x0000,  // Always fail.
135
        GrUserStencilOp::kZero,
136
        GrUserStencilOp::kReplace,
137
        0xffff>());
138
139
// Passes if the stencil value is nonzero. Also resets the stencil value to zero on pass. This is
140
// formulated to match kMarkStencil everywhere except the ref and compare mask. This will allow us
141
// to use the same pipeline for both stencil and fill if dynamic stencil state is supported.
142
constexpr static GrUserStencilSettings kTestAndResetStencil(
143
    GrUserStencilSettings::StaticInit<
144
        0x0000,
145
        GrUserStencilTest::kLessIfInClip,  // i.e., "not equal to zero, if in clip".
146
        0x0001,
147
        GrUserStencilOp::kZero,
148
        GrUserStencilOp::kReplace,
149
        0xffff>());
150
151
0
bool can_use_hardware_tessellation(int numVerbs, const GrPipeline& pipeline, const GrCaps& caps) {
152
0
    if (!caps.shaderCaps()->tessellationSupport() ||
153
0
        !caps.shaderCaps()->infinitySupport() /* The hw tessellation shaders use infinity. */) {
154
0
        return false;
155
0
    }
156
0
    if (pipeline.usesLocalCoords()) {
157
        // Our back door for HW tessellation shaders isn't currently capable of passing varyings to
158
        // the fragment shader, so if the processors have varyings, we need to use instanced draws
159
        // instead.
160
0
        return false;
161
0
    }
162
    // Only use hardware tessellation if we're drawing a somewhat large number of verbs. Otherwise
163
    // we seem to be better off using instanced draws.
164
0
    return numVerbs >= caps.minStrokeVerbsForHwTessellation();
165
0
}
166
167
void GrStrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramArgs&& args,
168
0
                                                 GrAppliedClip&& clip) {
169
0
    SkASSERT(!fTessellator);
170
0
    SkASSERT(!fFillProgram);
171
0
    SkASSERT(!fStencilProgram);
172
    // GrOp::setClippedBounds() should have been called by now.
173
0
    SkASSERT(SkRect::MakeIWH(args.fWriteView.width(),
174
0
                             args.fWriteView.height()).contains(this->bounds()));
175
176
0
    const GrCaps& caps = *args.fCaps;
177
0
    SkArenaAlloc* arena = args.fArena;
178
179
0
    std::array<float, 2> matrixMinMaxScales;
180
0
    if (!fViewMatrix.getMinMaxScales(matrixMinMaxScales.data())) {
181
0
        matrixMinMaxScales.fill(1);
182
0
    }
183
184
0
    float devInflationRadius = fInflationRadius;
185
0
    if (!this->headStroke().isHairlineStyle()) {
186
0
        devInflationRadius *= matrixMinMaxScales[1];
187
0
    }
188
0
    SkRect strokeCullBounds = this->bounds().makeOutset(devInflationRadius, devInflationRadius);
189
190
0
    auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(clip),
191
0
                                                        std::move(fProcessors));
192
193
0
    if (can_use_hardware_tessellation(fTotalCombinedVerbCnt, *pipeline, caps)) {
194
        // Only use hardware tessellation if we're drawing a somewhat large number of verbs.
195
        // Otherwise we seem to be better off using instanced draws.
196
0
        fTessellator = arena->make<GrStrokeHardwareTessellator>(*caps.shaderCaps(), fShaderFlags,
197
0
                                                                fViewMatrix, &fPathStrokeList,
198
0
                                                                matrixMinMaxScales,
199
0
                                                                strokeCullBounds);
200
0
    } else {
201
0
        fTessellator = arena->make<GrStrokeFixedCountTessellator>(*caps.shaderCaps(), fShaderFlags,
202
0
                                                                  fViewMatrix, &fPathStrokeList,
203
0
                                                                  matrixMinMaxScales,
204
0
                                                                  strokeCullBounds);
205
0
    }
206
207
0
    auto fillStencil = &GrUserStencilSettings::kUnused;
208
0
    if (fNeedsStencil) {
209
0
        fStencilProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(), pipeline,
210
0
                                                            &kMarkStencil);
211
0
        fillStencil = &kTestAndResetStencil;
212
0
        args.fXferBarrierFlags = GrXferBarrierFlags::kNone;
213
0
    }
214
215
0
    fFillProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(), pipeline,
216
0
                                                     fillStencil);
217
0
}
Unexecuted instantiation: GrStrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramArgs&&, GrAppliedClip&&)
Unexecuted instantiation: GrStrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramArgs&&, GrAppliedClip&&)
218
219
void GrStrokeTessellateOp::onPrePrepare(GrRecordingContext* context,
220
                                        const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
221
                                        const GrDstProxyView& dstProxyView,
222
                                        GrXferBarrierFlags renderPassXferBarriers, GrLoadOp
223
0
                                        colorLoadOp) {
224
0
    this->prePrepareTessellator({context->priv().recordTimeAllocator(), writeView, &dstProxyView,
225
0
                                renderPassXferBarriers, colorLoadOp, context->priv().caps()},
226
0
                                (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
227
0
    if (fStencilProgram) {
228
0
        context->priv().recordProgramInfo(fStencilProgram);
229
0
    }
230
0
    if (fFillProgram) {
231
0
        context->priv().recordProgramInfo(fFillProgram);
232
0
    }
233
0
}
234
235
0
void GrStrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
236
0
    if (!fTessellator) {
237
0
        this->prePrepareTessellator({flushState->allocator(), flushState->writeView(),
238
0
                                    &flushState->dstProxyView(), flushState->renderPassBarriers(),
239
0
                                    flushState->colorLoadOp(), &flushState->caps()},
240
0
                                    flushState->detachAppliedClip());
241
0
    }
242
0
    SkASSERT(fTessellator);
243
0
    fTessellator->prepare(flushState, fTotalCombinedVerbCnt);
244
0
}
Unexecuted instantiation: GrStrokeTessellateOp::onPrepare(GrOpFlushState*)
Unexecuted instantiation: GrStrokeTessellateOp::onPrepare(GrOpFlushState*)
245
246
0
void GrStrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
247
0
    if (fStencilProgram) {
248
0
        flushState->bindPipelineAndScissorClip(*fStencilProgram, chainBounds);
249
0
        flushState->bindTextures(fStencilProgram->geomProc(), nullptr, fStencilProgram->pipeline());
250
0
        fTessellator->draw(flushState);
251
0
    }
252
0
    if (fFillProgram) {
253
0
        flushState->bindPipelineAndScissorClip(*fFillProgram, chainBounds);
254
0
        flushState->bindTextures(fFillProgram->geomProc(), nullptr, fFillProgram->pipeline());
255
0
        fTessellator->draw(flushState);
256
0
    }
257
0
}