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