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