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