blob: afb49e4e065b10e2953a5309ccdf731a0e696f69 [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"
18#include "src/gpu/tessellate/GrStencilPathShader.h"
19
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*,
66 const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
67 GrLoadOp colorLoadOp) override {}
68 void onPrepare(GrOpFlushState* flushState) override {
69 auto alloc = flushState->allocator();
70 GrPathShader* shader;
71 switch (fMode) {
72 case Mode::kCurveMiddleOut:
73 fTessellator = alloc->make<GrPathIndirectTessellator>(
Chris Dalton2758a312021-05-20 10:46:25 -060074 fMatrix, fPath, GrPathTessellator::DrawInnerFan::kYes);
Chris Daltona9f759d2021-05-18 12:37:08 -060075 shader = alloc->make<GrCurveMiddleOutShader>(fMatrix);
76 break;
77 case Mode::kWedgeTessellate:
78 fTessellator = alloc->make<GrPathWedgeTessellator>();
79 shader = alloc->make<GrWedgeTessellateShader>(fMatrix);
80 break;
81 case Mode::kCurveTessellate:
Chris Dalton2758a312021-05-20 10:46:25 -060082 fTessellator = alloc->make<GrPathOuterCurveTessellator>(
83 GrPathTessellator::DrawInnerFan::kYes);
Chris Daltona9f759d2021-05-18 12:37:08 -060084 shader = alloc->make<GrCurveTessellateShader>(fMatrix);
85 break;
86 }
Chris Dalton8447f132021-05-21 15:54:23 -060087 fTessellator->prepare(flushState, this->bounds(), fMatrix, fPath);
Chris Daltona9f759d2021-05-18 12:37:08 -060088 auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
89 fPipelineFlags);
90 fProgram = GrPathShader::MakeProgram({alloc, flushState->writeView(),
91 &flushState->dstProxyView(),
92 flushState->renderPassBarriers(), GrLoadOp::kClear,
93 &flushState->caps()}, shader, pipeline,
94 &GrUserStencilSettings::kUnused);
95 }
96 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
97 flushState->bindPipeline(*fProgram, chainBounds);
98 fTessellator->draw(flushState);
99 }
100
101 const SkPath fPath;
102 const SkMatrix fMatrix;
103 const GrPipeline::InputFlags fPipelineFlags;
104 const Mode fMode;
105 GrPathTessellator* fTessellator = nullptr;
106 GrProgramInfo* fProgram;
107 GrProcessorSet fProcessors{SkBlendMode::kSrcOver};
108
109 friend class GrOp; // For ctor.
110};
111
112} // namespace
113
114// This sample enables wireframe and visualizes the triangles generated by path tessellators.
115class SamplePathTessellators : public Sample {
116public:
117 SamplePathTessellators() {
118#if 0
119 // For viewing middle-out triangulations of the inner fan.
120 fPath.moveTo(1, 0);
121 int numSides = 32 * 3;
122 for (int i = 1; i < numSides; ++i) {
123 float theta = 2*3.1415926535897932384626433832785 * i / numSides;
124 fPath.lineTo(std::cos(theta), std::sin(theta));
125 }
126 fPath.transform(SkMatrix::Scale(200, 200));
127 fPath.transform(SkMatrix::Translate(300, 300));
128#else
129 fPath.moveTo(100, 500);
130 fPath.cubicTo(300, 400, -100, 300, 100, 200);
131 fPath.quadTo(250, 0, 400, 200);
132 fPath.conicTo(600, 350, 400, 500, fConicWeight);
133 fPath.close();
134#endif
135 }
136
137private:
138 void onDrawContent(SkCanvas*) override;
139 Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
140 bool onClick(Sample::Click*) override;
141 bool onChar(SkUnichar) override;
142
143 SkString name() override { return SkString("PathTessellators"); }
144
145 SkPath fPath;
146 GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kHWAntialias |
147 GrPipeline::InputFlags::kWireframe;
148 Mode fMode = Mode::kCurveMiddleOut;
149
150 float fConicWeight = .5;
151
152 class Click;
153};
154
155void SamplePathTessellators::onDrawContent(SkCanvas* canvas) {
156 canvas->clear(SK_ColorBLACK);
157
158 auto ctx = canvas->recordingContext();
159 GrSurfaceDrawContext* sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
160
161 SkString error;
162 if (!sdc || !ctx) {
163 error = "GPU Only.";
164 } else if (!GrTessellationPathRenderer::IsSupported(*ctx->priv().caps())) {
165 error = "GrTessellationPathRenderer not supported.";
166 } else if (fMode >= Mode::kWedgeTessellate &&
167 !ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
168 error.printf("%s requires hardware tessellation support.", ModeName(fMode));
169 }
170 if (!error.isEmpty()) {
171 canvas->clear(SK_ColorRED);
172 SkFont font(nullptr, 20);
173 SkPaint captionPaint;
174 captionPaint.setColor(SK_ColorWHITE);
175 canvas->drawString(error.c_str(), 10, 30, font, captionPaint);
176 return;
177 }
178
179 sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx,
180 sdc->asRenderTargetProxy()->getBoundsRect(),
181 fPath, canvas->getTotalMatrix(),
182 fPipelineFlags, fMode));
183
184 // Draw the path points.
185 SkPaint pointsPaint;
186 pointsPaint.setColor(SK_ColorBLUE);
187 pointsPaint.setStrokeWidth(8);
188 SkPath devPath = fPath;
189 devPath.transform(canvas->getTotalMatrix());
190 {
191 SkAutoCanvasRestore acr(canvas, true);
192 canvas->setMatrix(SkMatrix::I());
193 SkString caption(ModeName(fMode));
194 caption.appendf(" (w=%g)", fConicWeight);
195 SkFont font(nullptr, 20);
196 SkPaint captionPaint;
197 captionPaint.setColor(SK_ColorWHITE);
198 canvas->drawString(caption, 10, 30, font, captionPaint);
199 canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
200 SkPathPriv::PointData(devPath), pointsPaint);
201 }
202}
203
204class SamplePathTessellators::Click : public Sample::Click {
205public:
206 Click(int ptIdx) : fPtIdx(ptIdx) {}
207
208 void doClick(SkPath* path) {
209 SkPoint pt = path->getPoint(fPtIdx);
210 SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev);
211 }
212
213private:
214 int fPtIdx;
215};
216
217Sample::Click* SamplePathTessellators::onFindClickHandler(SkScalar x, SkScalar y,
218 skui::ModifierKey) {
219 const SkPoint* pts = SkPathPriv::PointData(fPath);
220 float fuzz = 30;
221 for (int i = 0; i < fPath.countPoints(); ++i) {
222 if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) {
223 return new Click(i);
224 }
225 }
226 return nullptr;
227}
228
229bool SamplePathTessellators::onClick(Sample::Click* click) {
230 Click* myClick = (Click*)click;
231 myClick->doClick(&fPath);
232 return true;
233}
234
235static SkPath update_weight(const SkPath& path, float w) {
236 SkPath path_;
237 for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
238 switch (verb) {
239 case SkPathVerb::kMove:
240 path_.moveTo(pts[0]);
241 break;
242 case SkPathVerb::kLine:
243 path_.lineTo(pts[1]);
244 break;
245 case SkPathVerb::kQuad:
246 path_.quadTo(pts[1], pts[2]);
247 break;
248 case SkPathVerb::kCubic:
249 path_.cubicTo(pts[1], pts[2], pts[3]);
250 break;
251 case SkPathVerb::kConic:
252 path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f);
253 break;
254 case SkPathVerb::kClose:
255 break;
256 }
257 }
258 return path_;
259}
260
261bool SamplePathTessellators::onChar(SkUnichar unichar) {
262 switch (unichar) {
263 case 'w':
264 fPipelineFlags = (GrPipeline::InputFlags)(
265 (int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe);
266 return true;
267 case 'D': {
268 fPath.dump();
269 return true;
270 }
271 case '+':
272 fConicWeight *= 2;
273 fPath = update_weight(fPath, fConicWeight);
274 return true;
275 case '=':
276 fConicWeight *= 5/4.f;
277 fPath = update_weight(fPath, fConicWeight);
278 return true;
279 case '_':
280 fConicWeight *= .5f;
281 fPath = update_weight(fPath, fConicWeight);
282 return true;
283 case '-':
284 fConicWeight *= 4/5.f;
285 fPath = update_weight(fPath, fConicWeight);
286 return true;
287 case '1':
288 case '2':
289 case '3':
290 fMode = (Mode)(unichar - '1');
291 return true;
292 }
293 return false;
294}
295
296Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; }
297static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample);
298
299#endif // SK_SUPPORT_GPU