blob: e3978402b58e486819ef01015781e2f57ae07b76 [file] [log] [blame]
Chris Daltona9f759d2021-05-18 12:37:08 -06001/*
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/GrRecordingContextPriv.h"
16#include "src/gpu/GrSurfaceDrawContext.h"
17#include "src/gpu/tessellate/GrPathTessellator.h"
Chris Dalton3b412782021-06-01 13:40:03 -060018#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
Chris Daltona9f759d2021-05-18 12:37:08 -060019
20namespace {
21
22enum class Mode {
23 kCurveMiddleOut,
24 kWedgeTessellate,
25 kCurveTessellate
26};
27
28static const char* ModeName(Mode mode) {
29 switch (mode) {
30 case Mode::kCurveMiddleOut:
31 return "GrCurveMiddleOutShader";
32 case Mode::kWedgeTessellate:
33 return "GrWedgeTessellateShader";
34 case Mode::kCurveTessellate:
35 return "GrCurveTessellateShader";
36 }
37 SkUNREACHABLE;
38}
39
40// Draws a path directly to the screen using a specific tessellator.
41class SamplePathTessellatorOp : public GrDrawOp {
42private:
43 DEFINE_OP_CLASS_ID
44
45 SamplePathTessellatorOp(const SkRect& drawBounds, const SkPath& path, const SkMatrix& m,
46 GrPipeline::InputFlags pipelineFlags, Mode mode)
47 : GrDrawOp(ClassID())
48 , fPath(path)
49 , fMatrix(m)
50 , fPipelineFlags(pipelineFlags)
51 , fMode(mode) {
52 this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
53 }
54 const char* name() const override { return "SamplePathTessellatorOp"; }
55 void visitProxies(const VisitProxyFunc& fn) const override {}
56 FixedFunctionFlags fixedFunctionFlags() const override {
57 return FixedFunctionFlags::kUsesHWAA;
58 }
59 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
60 GrClampType clampType) override {
61 SkPMColor4f color;
62 return fProcessors.finalize(SK_PMColor4fWHITE, GrProcessorAnalysisCoverage::kNone, clip,
63 nullptr, caps, clampType, &color);
64 }
65 void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
John Stiles52cb1d02021-06-02 11:58:05 -040066 const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override {}
Chris Daltona9f759d2021-05-18 12:37:08 -060067 void onPrepare(GrOpFlushState* flushState) override {
Chris Dalton2f733ec2021-06-01 12:11:57 -060068 constexpr static SkPMColor4f kCyan = {0,1,1,1};
Chris Daltona9f759d2021-05-18 12:37:08 -060069 auto alloc = flushState->allocator();
Chris Daltona9f759d2021-05-18 12:37:08 -060070 switch (fMode) {
71 case Mode::kCurveMiddleOut:
Chris Dalton569c01b2021-05-25 10:11:46 -060072 fTessellator = GrPathIndirectTessellator::Make(
Chris Dalton2f733ec2021-06-01 12:11:57 -060073 alloc, fPath, fMatrix, kCyan, GrPathTessellator::DrawInnerFan::kYes);
Chris Daltona9f759d2021-05-18 12:37:08 -060074 break;
75 case Mode::kWedgeTessellate:
Chris Dalton2f733ec2021-06-01 12:11:57 -060076 fTessellator = GrPathWedgeTessellator::Make(alloc, fMatrix, kCyan);
Chris Daltona9f759d2021-05-18 12:37:08 -060077 break;
78 case Mode::kCurveTessellate:
Chris Dalton569c01b2021-05-25 10:11:46 -060079 fTessellator = GrPathOuterCurveTessellator::Make(
Chris Dalton2f733ec2021-06-01 12:11:57 -060080 alloc, fMatrix, kCyan, GrPathTessellator::DrawInnerFan::kYes);
Chris Daltona9f759d2021-05-18 12:37:08 -060081 break;
82 }
Chris Dalton569c01b2021-05-25 10:11:46 -060083 fTessellator->prepare(flushState, this->bounds(), fPath);
Chris Daltona9f759d2021-05-18 12:37:08 -060084 auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
85 fPipelineFlags);
Chris Dalton2f733ec2021-06-01 12:11:57 -060086 fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(),
87 &flushState->dstProxyView(),
88 flushState->renderPassBarriers(),
89 GrLoadOp::kClear, &flushState->caps()},
90 fTessellator->shader(), pipeline,
91 &GrUserStencilSettings::kUnused);
Chris Daltona9f759d2021-05-18 12:37:08 -060092 }
93 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
94 flushState->bindPipeline(*fProgram, chainBounds);
95 fTessellator->draw(flushState);
96 }
97
98 const SkPath fPath;
99 const SkMatrix fMatrix;
100 const GrPipeline::InputFlags fPipelineFlags;
101 const Mode fMode;
102 GrPathTessellator* fTessellator = nullptr;
103 GrProgramInfo* fProgram;
104 GrProcessorSet fProcessors{SkBlendMode::kSrcOver};
105
106 friend class GrOp; // For ctor.
107};
108
109} // namespace
110
111// This sample enables wireframe and visualizes the triangles generated by path tessellators.
112class SamplePathTessellators : public Sample {
113public:
114 SamplePathTessellators() {
115#if 0
116 // For viewing middle-out triangulations of the inner fan.
117 fPath.moveTo(1, 0);
118 int numSides = 32 * 3;
119 for (int i = 1; i < numSides; ++i) {
120 float theta = 2*3.1415926535897932384626433832785 * i / numSides;
121 fPath.lineTo(std::cos(theta), std::sin(theta));
122 }
123 fPath.transform(SkMatrix::Scale(200, 200));
124 fPath.transform(SkMatrix::Translate(300, 300));
125#else
126 fPath.moveTo(100, 500);
127 fPath.cubicTo(300, 400, -100, 300, 100, 200);
128 fPath.quadTo(250, 0, 400, 200);
129 fPath.conicTo(600, 350, 400, 500, fConicWeight);
130 fPath.close();
131#endif
132 }
133
134private:
135 void onDrawContent(SkCanvas*) override;
136 Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
137 bool onClick(Sample::Click*) override;
138 bool onChar(SkUnichar) override;
139
140 SkString name() override { return SkString("PathTessellators"); }
141
142 SkPath fPath;
143 GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kHWAntialias |
144 GrPipeline::InputFlags::kWireframe;
145 Mode fMode = Mode::kCurveMiddleOut;
146
147 float fConicWeight = .5;
148
149 class Click;
150};
151
152void SamplePathTessellators::onDrawContent(SkCanvas* canvas) {
153 canvas->clear(SK_ColorBLACK);
154
155 auto ctx = canvas->recordingContext();
156 GrSurfaceDrawContext* sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
157
158 SkString error;
159 if (!sdc || !ctx) {
160 error = "GPU Only.";
161 } else if (!GrTessellationPathRenderer::IsSupported(*ctx->priv().caps())) {
162 error = "GrTessellationPathRenderer not supported.";
163 } else if (fMode >= Mode::kWedgeTessellate &&
164 !ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
165 error.printf("%s requires hardware tessellation support.", ModeName(fMode));
166 }
167 if (!error.isEmpty()) {
168 canvas->clear(SK_ColorRED);
169 SkFont font(nullptr, 20);
170 SkPaint captionPaint;
171 captionPaint.setColor(SK_ColorWHITE);
172 canvas->drawString(error.c_str(), 10, 30, font, captionPaint);
173 return;
174 }
175
176 sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx,
177 sdc->asRenderTargetProxy()->getBoundsRect(),
178 fPath, canvas->getTotalMatrix(),
179 fPipelineFlags, fMode));
180
181 // Draw the path points.
182 SkPaint pointsPaint;
183 pointsPaint.setColor(SK_ColorBLUE);
184 pointsPaint.setStrokeWidth(8);
185 SkPath devPath = fPath;
186 devPath.transform(canvas->getTotalMatrix());
187 {
188 SkAutoCanvasRestore acr(canvas, true);
189 canvas->setMatrix(SkMatrix::I());
190 SkString caption(ModeName(fMode));
191 caption.appendf(" (w=%g)", fConicWeight);
192 SkFont font(nullptr, 20);
193 SkPaint captionPaint;
194 captionPaint.setColor(SK_ColorWHITE);
195 canvas->drawString(caption, 10, 30, font, captionPaint);
196 canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
197 SkPathPriv::PointData(devPath), pointsPaint);
198 }
199}
200
201class SamplePathTessellators::Click : public Sample::Click {
202public:
203 Click(int ptIdx) : fPtIdx(ptIdx) {}
204
205 void doClick(SkPath* path) {
206 SkPoint pt = path->getPoint(fPtIdx);
207 SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev);
208 }
209
210private:
211 int fPtIdx;
212};
213
214Sample::Click* SamplePathTessellators::onFindClickHandler(SkScalar x, SkScalar y,
215 skui::ModifierKey) {
216 const SkPoint* pts = SkPathPriv::PointData(fPath);
217 float fuzz = 30;
218 for (int i = 0; i < fPath.countPoints(); ++i) {
219 if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) {
220 return new Click(i);
221 }
222 }
223 return nullptr;
224}
225
226bool SamplePathTessellators::onClick(Sample::Click* click) {
227 Click* myClick = (Click*)click;
228 myClick->doClick(&fPath);
229 return true;
230}
231
232static SkPath update_weight(const SkPath& path, float w) {
233 SkPath path_;
234 for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
235 switch (verb) {
236 case SkPathVerb::kMove:
237 path_.moveTo(pts[0]);
238 break;
239 case SkPathVerb::kLine:
240 path_.lineTo(pts[1]);
241 break;
242 case SkPathVerb::kQuad:
243 path_.quadTo(pts[1], pts[2]);
244 break;
245 case SkPathVerb::kCubic:
246 path_.cubicTo(pts[1], pts[2], pts[3]);
247 break;
248 case SkPathVerb::kConic:
249 path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f);
250 break;
251 case SkPathVerb::kClose:
252 break;
253 }
254 }
255 return path_;
256}
257
258bool SamplePathTessellators::onChar(SkUnichar unichar) {
259 switch (unichar) {
260 case 'w':
261 fPipelineFlags = (GrPipeline::InputFlags)(
262 (int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe);
263 return true;
264 case 'D': {
265 fPath.dump();
266 return true;
267 }
268 case '+':
269 fConicWeight *= 2;
270 fPath = update_weight(fPath, fConicWeight);
271 return true;
272 case '=':
273 fConicWeight *= 5/4.f;
274 fPath = update_weight(fPath, fConicWeight);
275 return true;
276 case '_':
277 fConicWeight *= .5f;
278 fPath = update_weight(fPath, fConicWeight);
279 return true;
280 case '-':
281 fConicWeight *= 4/5.f;
282 fPath = update_weight(fPath, fConicWeight);
283 return true;
284 case '1':
285 case '2':
286 case '3':
287 fMode = (Mode)(unichar - '1');
288 return true;
289 }
290 return false;
291}
292
293Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; }
294static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample);
295
296#endif // SK_SUPPORT_GPU