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