1/*
2 * Copyright 2019 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 "include/core/SkCanvas.h"
9#include "samplecode/Sample.h"
10#include "src/core/SkPathPriv.h"
11
12#if SK_SUPPORT_GPU
13
14#include "src/core/SkCanvasPriv.h"
15#include "src/gpu/GrOpFlushState.h"
16#include "src/gpu/GrRecordingContextPriv.h"
17#include "src/gpu/ops/GrDrawOp.h"
18#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
19#include "src/gpu/ops/TessellationPathRenderer.h"
20#include "src/gpu/tessellate/AffineMatrix.h"
21#include "src/gpu/tessellate/PathCurveTessellator.h"
22#include "src/gpu/tessellate/PathWedgeTessellator.h"
23#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
24#include "src/gpu/v1/SurfaceDrawContext_v1.h"
25
26namespace skgpu {
27
28using TrianglePatch = PatchWriter::TrianglePatch;
29
30namespace {
31
32enum class Mode {
33    kWedgeMiddleOut,
34    kCurveMiddleOut,
35    kWedgeTessellate,
36    kCurveTessellate
37};
38
39static const char* ModeName(Mode mode) {
40    switch (mode) {
41        case Mode::kWedgeMiddleOut:
42            return "MiddleOutShader (kWedges)";
43        case Mode::kCurveMiddleOut:
44            return "MiddleOutShader (kCurves)";
45        case Mode::kWedgeTessellate:
46            return "HardwareWedgeShader";
47        case Mode::kCurveTessellate:
48            return "HardwareCurveShader";
49    }
50    SkUNREACHABLE;
51}
52
53// Draws a path directly to the screen using a specific tessellator.
54class SamplePathTessellatorOp : public GrDrawOp {
55private:
56    DEFINE_OP_CLASS_ID
57
58    SamplePathTessellatorOp(const SkRect& drawBounds, const SkPath& path, const SkMatrix& m,
59                            GrPipeline::InputFlags pipelineFlags, Mode mode)
60            : GrDrawOp(ClassID())
61            , fPath(path)
62            , fMatrix(m)
63            , fPipelineFlags(pipelineFlags)
64            , fMode(mode) {
65        this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
66    }
67    const char* name() const override { return "SamplePathTessellatorOp"; }
68    void visitProxies(const GrVisitProxyFunc&) const override {}
69    FixedFunctionFlags fixedFunctionFlags() const override {
70        return FixedFunctionFlags::kUsesHWAA;
71    }
72    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
73                                      GrClampType clampType) override {
74        SkPMColor4f color;
75        return fProcessors.finalize(SK_PMColor4fWHITE, GrProcessorAnalysisCoverage::kNone, clip,
76                                    nullptr, caps, clampType, &color);
77    }
78    void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
79                      const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override {}
80    void onPrepare(GrOpFlushState* flushState) override {
81        constexpr static SkPMColor4f kCyan = {0,1,1,1};
82        auto alloc = flushState->allocator();
83        const SkMatrix& shaderMatrix = SkMatrix::I();
84        const SkMatrix& pathMatrix = fMatrix;
85        const GrCaps& caps = flushState->caps();
86        const GrShaderCaps& shaderCaps = *caps.shaderCaps();
87        int numVerbsToGetMiddleOut = 0;
88        int numVerbsToGetTessellation = caps.minPathVerbsForHwTessellation();
89        auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
90                                                                 fPipelineFlags);
91        int numVerbs;
92        bool needsInnerFan;
93        switch (fMode) {
94            case Mode::kWedgeMiddleOut:
95                fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport());
96                numVerbs = numVerbsToGetMiddleOut;
97                needsInnerFan = false;
98                break;
99            case Mode::kCurveMiddleOut:
100                fTessellator = PathCurveTessellator::Make(alloc,
101                                                          shaderCaps.infinitySupport());
102                numVerbs = numVerbsToGetMiddleOut;
103                needsInnerFan = true;
104                break;
105            case Mode::kWedgeTessellate:
106                fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport());
107                numVerbs = numVerbsToGetTessellation;
108                needsInnerFan = false;
109                break;
110            case Mode::kCurveTessellate:
111                fTessellator = PathCurveTessellator::Make(alloc,
112                                                          shaderCaps.infinitySupport());
113                numVerbs = numVerbsToGetTessellation;
114                needsInnerFan = true;
115                break;
116        }
117        auto* tessShader = GrPathTessellationShader::Make(alloc,
118                                                          shaderMatrix,
119                                                          kCyan,
120                                                          numVerbs,
121                                                          *pipeline,
122                                                          fTessellator->patchAttribs(),
123                                                          caps);
124        fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(),
125                                                     flushState->usesMSAASurface(),
126                                                     &flushState->dstProxyView(),
127                                                     flushState->renderPassBarriers(),
128                                                     GrLoadOp::kClear, &flushState->caps()},
129                                                     tessShader,
130                                                     pipeline,
131                                                     &GrUserStencilSettings::kUnused);
132
133
134        int patchPreallocCount = fTessellator->patchPreallocCount(fPath.countVerbs());
135        if (needsInnerFan) {
136            patchPreallocCount += fPath.countVerbs() - 1;
137        }
138        PatchWriter patchWriter(flushState, fTessellator, patchPreallocCount);
139
140        if (needsInnerFan) {
141            // Write out inner fan triangles.
142            AffineMatrix m(pathMatrix);
143            for (PathMiddleOutFanIter it(fPath); !it.done();) {
144                for (auto [p0, p1, p2] : it.nextStack()) {
145                    TrianglePatch(patchWriter) << m.map2Points(p0, p1) << m.mapPoint(p2);
146                }
147            }
148        }
149
150        // Write out the curves.
151        fTessellator->writePatches(patchWriter,
152                                   tessShader->maxTessellationSegments(*caps.shaderCaps()),
153                                   shaderMatrix,
154                                   {pathMatrix, fPath, kCyan});
155
156        if (!tessShader->willUseTessellationShaders()) {
157            fTessellator->prepareFixedCountBuffers(flushState);
158        }
159
160    }
161    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
162        flushState->bindPipeline(*fProgram, chainBounds);
163        fTessellator->draw(flushState, fProgram->geomProc().willUseTessellationShaders());
164    }
165
166    const SkPath fPath;
167    const SkMatrix fMatrix;
168    const GrPipeline::InputFlags fPipelineFlags;
169    const Mode fMode;
170    PathTessellator* fTessellator = nullptr;
171    GrProgramInfo* fProgram;
172    GrProcessorSet fProcessors{SkBlendMode::kSrcOver};
173
174    friend class GrOp;  // For ctor.
175};
176
177}  // namespace
178
179// This sample enables wireframe and visualizes the triangles generated by path tessellators.
180class SamplePathTessellators : public Sample {
181public:
182    SamplePathTessellators() {
183#if 0
184        // For viewing middle-out triangulations of the inner fan.
185        fPath.moveTo(1, 0);
186        int numSides = 32 * 3;
187        for (int i = 1; i < numSides; ++i) {
188            float theta = 2*3.1415926535897932384626433832785 * i / numSides;
189            fPath.lineTo(std::cos(theta), std::sin(theta));
190        }
191        fPath.transform(SkMatrix::Scale(200, 200));
192        fPath.transform(SkMatrix::Translate(300, 300));
193#else
194        fPath.moveTo(100, 500);
195        fPath.cubicTo(300, 400, -100, 300, 100, 200);
196        fPath.quadTo(250, 0, 400, 200);
197        fPath.conicTo(600, 350, 400, 500, fConicWeight);
198        fPath.close();
199#endif
200    }
201
202private:
203    void onDrawContent(SkCanvas*) override;
204    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
205    bool onClick(Sample::Click*) override;
206    bool onChar(SkUnichar) override;
207
208    SkString name() override { return SkString("PathTessellators"); }
209
210    SkPath fPath;
211    GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kWireframe;
212    Mode fMode = Mode::kWedgeMiddleOut;
213
214    float fConicWeight = .5;
215
216    class Click;
217};
218
219void SamplePathTessellators::onDrawContent(SkCanvas* canvas) {
220    canvas->clear(SK_ColorBLACK);
221
222    auto ctx = canvas->recordingContext();
223    auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
224
225    SkString error;
226    if (!sdc || !ctx) {
227        error = "GPU Only.";
228    } else if (!skgpu::v1::TessellationPathRenderer::IsSupported(*ctx->priv().caps())) {
229        error = "TessellationPathRenderer not supported.";
230    } else if (fMode >= Mode::kWedgeTessellate &&
231               !ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
232        error.printf("%s requires hardware tessellation support.", ModeName(fMode));
233    }
234    if (!error.isEmpty()) {
235        canvas->clear(SK_ColorRED);
236        SkFont font(nullptr, 20);
237        SkPaint captionPaint;
238        captionPaint.setColor(SK_ColorWHITE);
239        canvas->drawString(error.c_str(), 10, 30, font, captionPaint);
240        return;
241    }
242
243    sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx,
244                                                       sdc->asRenderTargetProxy()->getBoundsRect(),
245                                                       fPath, canvas->getTotalMatrix(),
246                                                       fPipelineFlags, fMode));
247
248    // Draw the path points.
249    SkPaint pointsPaint;
250    pointsPaint.setColor(SK_ColorBLUE);
251    pointsPaint.setStrokeWidth(8);
252    SkPath devPath = fPath;
253    devPath.transform(canvas->getTotalMatrix());
254    {
255        SkAutoCanvasRestore acr(canvas, true);
256        canvas->setMatrix(SkMatrix::I());
257        SkString caption(ModeName(fMode));
258        caption.appendf(" (w=%g)", fConicWeight);
259        SkFont font(nullptr, 20);
260        SkPaint captionPaint;
261        captionPaint.setColor(SK_ColorWHITE);
262        canvas->drawString(caption, 10, 30, font, captionPaint);
263        canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
264                           SkPathPriv::PointData(devPath), pointsPaint);
265    }
266}
267
268class SamplePathTessellators::Click : public Sample::Click {
269public:
270    Click(int ptIdx) : fPtIdx(ptIdx) {}
271
272    void doClick(SkPath* path) {
273        SkPoint pt = path->getPoint(fPtIdx);
274        SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev);
275    }
276
277private:
278    int fPtIdx;
279};
280
281Sample::Click* SamplePathTessellators::onFindClickHandler(SkScalar x, SkScalar y,
282                                                          skui::ModifierKey) {
283    const SkPoint* pts = SkPathPriv::PointData(fPath);
284    float fuzz = 30;
285    for (int i = 0; i < fPath.countPoints(); ++i) {
286        if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) {
287            return new Click(i);
288        }
289    }
290    return nullptr;
291}
292
293bool SamplePathTessellators::onClick(Sample::Click* click) {
294    Click* myClick = (Click*)click;
295    myClick->doClick(&fPath);
296    return true;
297}
298
299static SkPath update_weight(const SkPath& path, float w) {
300    SkPath path_;
301    for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
302        switch (verb) {
303            case SkPathVerb::kMove:
304                path_.moveTo(pts[0]);
305                break;
306            case SkPathVerb::kLine:
307                path_.lineTo(pts[1]);
308                break;
309            case SkPathVerb::kQuad:
310                path_.quadTo(pts[1], pts[2]);
311                break;
312            case SkPathVerb::kCubic:
313                path_.cubicTo(pts[1], pts[2], pts[3]);
314                break;
315            case SkPathVerb::kConic:
316                path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f);
317                break;
318            case SkPathVerb::kClose:
319                break;
320        }
321    }
322    return path_;
323}
324
325bool SamplePathTessellators::onChar(SkUnichar unichar) {
326    switch (unichar) {
327        case 'w':
328            fPipelineFlags = (GrPipeline::InputFlags)(
329                    (int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe);
330            return true;
331        case 'D': {
332            fPath.dump();
333            return true;
334        }
335        case '+':
336            fConicWeight *= 2;
337            fPath = update_weight(fPath, fConicWeight);
338            return true;
339        case '=':
340            fConicWeight *= 5/4.f;
341            fPath = update_weight(fPath, fConicWeight);
342            return true;
343        case '_':
344            fConicWeight *= .5f;
345            fPath = update_weight(fPath, fConicWeight);
346            return true;
347        case '-':
348            fConicWeight *= 4/5.f;
349            fPath = update_weight(fPath, fConicWeight);
350            return true;
351        case '1':
352        case '2':
353        case '3':
354        case '4':
355            fMode = (Mode)(unichar - '1');
356            return true;
357    }
358    return false;
359}
360
361Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; }
362static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample);
363
364}  // namespace skgpu
365
366#endif  // SK_SUPPORT_GPU
367